Loki: Implement step editor (#69648)

* Loki: Implement step editor

* Update to keep value

* Remove console.log

* Remove white space

* Update public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx

Co-authored-by: Matias Chomicki <matyax@gmail.com>

* Import trim

* Update using of step in split queries

* Add tests

* Add tests

* Remove step interpolation

---------

Co-authored-by: Matias Chomicki <matyax@gmail.com>
This commit is contained in:
Ivana Huckova
2023-06-16 18:08:29 +02:00
committed by GitHub
parent 66d2214c3b
commit 82c125d450
8 changed files with 359 additions and 114 deletions
+4 -1
View File
@@ -126,7 +126,10 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) {
interval := query.Interval
timeRange := query.TimeRange.To.Sub(query.TimeRange.From)
step := calculateStep(interval, timeRange, resolution)
step, err := calculateStep(interval, timeRange, resolution, model.Step)
if err != nil {
return nil, err
}
expr := interpolateVariables(model.Expr, interval, timeRange)
+15 -6
View File
@@ -3,6 +3,8 @@ package loki
import (
"math"
"time"
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
)
// round the duration to the nearest millisecond larger-or-equal-to the duration
@@ -20,12 +22,19 @@ func durationMax(d1 time.Duration, d2 time.Duration) time.Duration {
}
}
func calculateStep(baseInterval time.Duration, timeRange time.Duration, resolution int64) time.Duration {
step := time.Duration(baseInterval.Nanoseconds() * resolution)
func calculateStep(interval time.Duration, timeRange time.Duration, resolution int64, queryStep *string) (time.Duration, error) {
// If we don't have step from query we calculate it from interval, time range and resolution
if queryStep == nil || *queryStep == "" {
step := time.Duration(interval.Nanoseconds() * resolution)
safeStep := timeRange / 11000
chosenStep := durationMax(step, safeStep)
return ceilMs(chosenStep), nil
}
safeStep := timeRange / 11000
step, err := intervalv2.ParseIntervalStringToTimeDuration(*queryStep)
if err != nil {
return step, err
}
chosenStep := durationMax(step, safeStep)
return ceilMs(chosenStep)
return time.Duration(step.Nanoseconds() * resolution), nil
}
+96 -35
View File
@@ -8,50 +8,111 @@ import (
)
func TestLokiStep(t *testing.T) {
t.Run("base case", func(t *testing.T) {
require.Equal(t, time.Second*14, calculateStep(time.Second*7, time.Second, 2))
})
t.Run("with query step", func(t *testing.T) {
t.Run("valid step in go duration format", func(t *testing.T) {
queryStep := "1m"
step, err := calculateStep(time.Second*7, time.Second, 2, &queryStep)
require.NoError(t, err)
require.Equal(t, time.Minute*2, step)
})
t.Run("step should be at least 1 millisecond", func(t *testing.T) {
require.Equal(t, time.Millisecond*1, calculateStep(time.Microsecond*500, time.Second, 1))
})
t.Run("valid step as number", func(t *testing.T) {
queryStep := "30"
step, err := calculateStep(time.Second*7, time.Second, 2, &queryStep)
require.NoError(t, err)
require.Equal(t, time.Minute*1, step)
})
t.Run("safeInterval should happen", func(t *testing.T) {
// safeInterval
require.Equal(t, time.Second*3, calculateStep(time.Second*2, time.Second*33000, 1))
})
// calculateStep parses a duration with support for unit that Grafana uses (e.g 1d)
t.Run("step with 1d", func(t *testing.T) {
queryStep := "1d"
step, err := calculateStep(time.Second*7, time.Second, 2, &queryStep)
require.NoError(t, err)
require.Equal(t, time.Hour*48, step)
})
t.Run("step should math.Ceil in milliseconds", func(t *testing.T) {
require.Equal(t, time.Millisecond*2, calculateStep(time.Microsecond*1234, time.Second*1, 1))
})
// calculateStep parses a duration with support for unit that Grafana uses (e.g 1w)
t.Run("step with 1w", func(t *testing.T) {
queryStep := "1w"
step, err := calculateStep(time.Second*7, time.Second, 2, &queryStep)
require.NoError(t, err)
require.Equal(t, time.Hour*336, step)
})
t.Run("step should math.Ceil in milliseconds, even if safeInterval happens", func(t *testing.T) {
require.Equal(t, time.Millisecond*3001, calculateStep(time.Second*2, time.Second*33001, 1))
// Returns error
t.Run("invalid step", func(t *testing.T) {
queryStep := "invalid"
step, err := calculateStep(time.Second*7, time.Second, 2, &queryStep)
require.Error(t, err)
require.Equal(t, time.Duration(0), step)
})
})
t.Run("with no query step", func(t *testing.T) {
t.Run("base case", func(t *testing.T) {
step, err := calculateStep(time.Second*7, time.Second, 2, nil)
require.NoError(t, err)
require.Equal(t, time.Second*14, step)
})
t.Run("resolution should happen", func(t *testing.T) {
require.Equal(t, time.Second*5, calculateStep(time.Second*1, time.Second*100, 5))
})
t.Run("step should be at least 1 millisecond", func(t *testing.T) {
step, err := calculateStep(time.Microsecond*500, time.Second, 1, nil)
require.NoError(t, err)
require.Equal(t, time.Millisecond*1, step)
})
t.Run("safeInterval check should happen after resolution is used", func(t *testing.T) {
require.Equal(t, time.Second*4, calculateStep(time.Second*2, time.Second*33000, 2))
})
t.Run("safeInterval should happen", func(t *testing.T) {
// safeInterval
step, err := calculateStep(time.Second*2, time.Second*33000, 1, nil)
require.NoError(t, err)
require.Equal(t, time.Second*3, step)
})
t.Run("survive interval=0", func(t *testing.T) {
// interval=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
require.Equal(t, time.Second*2, calculateStep(time.Second*0, time.Second*22000, 1))
})
t.Run("step should math.Ceil in milliseconds", func(t *testing.T) {
step, err := calculateStep(time.Microsecond*1234, time.Second*1, 1, nil)
require.NoError(t, err)
require.Equal(t, time.Millisecond*2, step)
})
t.Run("survive resolution=0", func(t *testing.T) {
// resolution=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
require.Equal(t, time.Second*2, calculateStep(time.Second*1, time.Second*22000, 0))
})
t.Run("step should math.Ceil in milliseconds, even if safeInterval happens", func(t *testing.T) {
step, err := calculateStep(time.Second*2, time.Second*33001, 1, nil)
require.NoError(t, err)
require.Equal(t, time.Millisecond*3001, step)
})
t.Run("survive interval=0 and resolution=0", func(t *testing.T) {
// resolution=0 and interval=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
require.Equal(t, time.Second*2, calculateStep(time.Second*0, time.Second*22000, 0))
t.Run("resolution should happen", func(t *testing.T) {
step, err := calculateStep(time.Second*1, time.Second*100, 5, nil)
require.NoError(t, err)
require.Equal(t, time.Second*5, step)
})
t.Run("safeInterval check should happen after resolution is used", func(t *testing.T) {
step, err := calculateStep(time.Second*2, time.Second*33000, 2, nil)
require.NoError(t, err)
require.Equal(t, time.Second*4, step)
})
t.Run("survive interval=0", func(t *testing.T) {
// interval=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
step, err := calculateStep(time.Second*0, time.Second*22000, 1, nil)
require.NoError(t, err)
require.Equal(t, time.Second*2, step)
})
t.Run("survive resolution=0", func(t *testing.T) {
// resolution=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
step, err := calculateStep(time.Second*1, time.Second*22000, 0, nil)
require.NoError(t, err)
require.Equal(t, time.Second*2, step)
})
t.Run("survive interval=0 and resolution=0", func(t *testing.T) {
// resolution=0 and interval=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
step, err := calculateStep(time.Second*0, time.Second*22000, 0, nil)
require.NoError(t, err)
require.Equal(t, time.Second*2, step)
})
})
}
@@ -40,7 +40,125 @@ describe('runSplitQuery()', () => {
});
});
test('Handles and reports rerrors', async () => {
test('Correctly splits queries without step', async () => {
await expect(runSplitQuery(datasource, request)).toEmitValuesWith(() => {
expect(datasource.runQuery).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
requestId: 'TEST_3',
intervalMs: 60000,
range: expect.objectContaining({
from: expect.objectContaining({
//2023-02-09T06:00:00.000Z
_i: 1675922400000,
}),
to: expect.objectContaining({
// 2023-02-10T06:00:00.000Z
_i: 1676008800000,
}),
}),
})
);
expect(datasource.runQuery).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
requestId: 'TEST_2',
intervalMs: 60000,
range: expect.objectContaining({
from: expect.objectContaining({
//2023-02-08T05:59:00.000Z
_i: 1675835940000,
}),
to: expect.objectContaining({
// 2023-02-09T05:59:00.000Z
_i: 1675922340000,
}),
}),
})
);
expect(datasource.runQuery).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
requestId: 'TEST_1',
intervalMs: 60000,
range: expect.objectContaining({
from: expect.objectContaining({
//2023-02-08T05:00:00.000Z
_i: 1675832400000,
}),
to: expect.objectContaining({
// 2023-02-08T05:58:00.000Z
_i: 1675835880000,
}),
}),
})
);
});
});
test('Correctly splits queries with step', async () => {
const req = { ...request };
req.targets[0].step = '10s';
await expect(runSplitQuery(datasource, request)).toEmitValuesWith(() => {
expect(datasource.runQuery).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
requestId: 'TEST_3',
intervalMs: 60000,
range: expect.objectContaining({
from: expect.objectContaining({
//2023-02-09T06:00:00.000Z
_i: 1675922400000,
}),
to: expect.objectContaining({
// 2023-02-10T06:00:00.000Z
_i: 1676008800000,
}),
}),
})
);
expect(datasource.runQuery).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
requestId: 'TEST_2',
intervalMs: 60000,
range: expect.objectContaining({
from: expect.objectContaining({
//2023-02-08T05:59:50.000Z
_i: 1675835990000,
}),
to: expect.objectContaining({
// 2023-02-09T05:59:50.000Z
_i: 1675922390000,
}),
}),
})
);
expect(datasource.runQuery).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
requestId: 'TEST_1',
intervalMs: 60000,
range: expect.objectContaining({
from: expect.objectContaining({
// 2023-02-08T05:00:00.000Z
_i: 1675832400000,
}),
to: expect.objectContaining({
// 2023-02-08T05:59:40.000Z
_i: 1675835980000,
}),
}),
})
);
});
});
test('Handles and reports errors', async () => {
jest
.spyOn(datasource, 'runQuery')
.mockReturnValue(of({ state: LoadingState.Error, error: { refId: 'A', message: 'Error' }, data: [] }));
@@ -339,7 +457,7 @@ describe('runSplitQuery()', () => {
expect(datasource.runQuery).toHaveBeenCalledTimes(2);
});
});
test('Groups metric queries by resolution', async () => {
test('Groups metric queries with no step by calculated stepMs', async () => {
const request = getQueryOptions<LokiQuery>({
targets: [
{ expr: 'count_over_time({a="b"}[1m])', refId: 'A', resolution: 3 },
@@ -352,23 +470,21 @@ describe('runSplitQuery()', () => {
expect(datasource.runQuery).toHaveBeenCalledTimes(2);
});
});
test('Groups mixed queries by resolution', async () => {
test('Groups metric queries with step by stepMs', async () => {
const request = getQueryOptions<LokiQuery>({
targets: [
{ expr: '{a="b"}', refId: 'A', resolution: 3 },
{ expr: '{a="b"}', refId: 'B', resolution: 5 },
{ expr: 'count_over_time({a="b"}[1m])', refId: 'C', resolution: 3 },
{ expr: 'count_over_time{a="b"}[1m])', refId: 'D', resolution: 5 },
{ expr: '{a="b"}', refId: 'E', resolution: 5, queryType: LokiQueryType.Instant },
{ expr: 'count_over_time({a="b"}[1m])', refId: 'A', resolution: 1, step: '10' },
{ expr: 'count_over_time{a="b"}[1m])', refId: 'B', resolution: 1, step: '5ms' },
],
range: range1d,
});
await expect(runSplitQuery(datasource, request)).toEmitValuesWith(() => {
// A, B, C, D, E
expect(datasource.runQuery).toHaveBeenCalledTimes(5);
// A, B
expect(datasource.runQuery).toHaveBeenCalledTimes(2);
});
});
test('Chunked groups mixed queries by resolution', async () => {
test('Groups mixed queries by stepMs', async () => {
const request = getQueryOptions<LokiQuery>({
targets: [
{ expr: '{a="b"}', refId: 'A', resolution: 3 },
@@ -376,12 +492,32 @@ describe('runSplitQuery()', () => {
{ expr: 'count_over_time({a="b"}[1m])', refId: 'C', resolution: 3 },
{ expr: 'count_over_time{a="b"}[1m])', refId: 'D', resolution: 5 },
{ expr: '{a="b"}', refId: 'E', resolution: 5, queryType: LokiQueryType.Instant },
{ expr: 'rate({a="b"}[5m])', refId: 'F', resolution: 5, step: '10' },
{ expr: 'rate({a="b"} | logfmt[5m])', refId: 'G', resolution: 5, step: '10s' },
],
range: range1d,
});
await expect(runSplitQuery(datasource, request)).toEmitValuesWith(() => {
// A, B, C, D, E, F+G
expect(datasource.runQuery).toHaveBeenCalledTimes(6);
});
});
test('Chunked groups mixed queries by stepMs', async () => {
const request = getQueryOptions<LokiQuery>({
targets: [
{ expr: '{a="b"}', refId: 'A', resolution: 3 },
{ expr: '{a="b"}', refId: 'B', resolution: 5 },
{ expr: 'count_over_time({a="b"}[1m])', refId: 'C', resolution: 3 },
{ expr: 'count_over_time{a="b"}[1m])', refId: 'D', resolution: 5 },
{ expr: '{a="b"}', refId: 'E', resolution: 5, queryType: LokiQueryType.Instant },
{ expr: 'rate({a="b"}[5m])', refId: 'F', resolution: 5, step: '10' },
{ expr: 'rate({a="b"} | logfmt[5m])', refId: 'G', resolution: 5, step: '10s' },
],
range, // 3 days
});
await expect(runSplitQuery(datasource, request)).toEmitValuesWith(() => {
// 3 * A, 3 * B, 3 * C, 3 * D, 1 * E
expect(datasource.runQuery).toHaveBeenCalledTimes(13);
// 3 * A, 3 * B, 3 * C, 3 * D, 1 * E, 3 * F+G
expect(datasource.runQuery).toHaveBeenCalledTimes(16);
});
});
});
@@ -10,6 +10,7 @@ import {
dateTime,
durationToMilliseconds,
parseDuration,
rangeUtil,
TimeRange,
} from '@grafana/data';
import { LoadingState } from '@grafana/schema';
@@ -25,24 +26,15 @@ import { LokiGroupedRequest, LokiQuery, LokiQueryType } from './types';
export function partitionTimeRange(
isLogsQuery: boolean,
originalTimeRange: TimeRange,
intervalMs: number,
resolution: number,
stepMs: number,
duration: number
): TimeRange[] {
// the `step` value that will be finally sent to Loki is rougly the same as `intervalMs`,
// but there are some complications.
// we need to replicate this algo:
//
// https://github.com/grafana/grafana/blob/main/pkg/tsdb/loki/step.go#L23
const start = originalTimeRange.from.toDate().getTime();
const end = originalTimeRange.to.toDate().getTime();
const safeStep = Math.ceil((end - start) / 11000);
const step = Math.max(intervalMs * resolution, safeStep);
const ranges = isLogsQuery
? splitLogsTimeRange(start, end, duration)
: splitMetricTimeRange(start, end, step, duration);
: splitMetricTimeRange(start, end, stepMs, duration);
return ranges.map(([start, end]) => {
const from = dateTime(start);
@@ -243,29 +235,20 @@ export function runSplitQuery(datasource: LokiDatasource, request: DataQueryRequ
for (const resolution in resolutionPartition) {
requests.push({
request: { ...request, targets: resolutionPartition[resolution] },
partition: partitionTimeRange(
true,
request.range,
request.intervalMs,
Number(resolution),
Number(chunkRangeMs)
),
partition: partitionTimeRange(true, request.range, request.intervalMs, Number(chunkRangeMs)),
});
}
}
for (const [chunkRangeMs, queries] of Object.entries(rangePartitionedMetricQueries)) {
const resolutionPartition = groupBy(queries, (query) => query.resolution || 1);
for (const resolution in resolutionPartition) {
const stepMsPartition = groupBy(queries, (query) =>
calculateStep(request.intervalMs, request.range, query.resolution || 1, query.step)
);
for (const stepMs in stepMsPartition) {
requests.push({
request: { ...request, targets: resolutionPartition[resolution] },
partition: partitionTimeRange(
false,
request.range,
request.intervalMs,
Number(resolution),
Number(chunkRangeMs)
),
request: { ...request, targets: stepMsPartition[stepMs] },
partition: partitionTimeRange(false, request.range, Number(stepMs), Number(chunkRangeMs)),
});
}
}
@@ -288,3 +271,18 @@ export function runSplitQuery(datasource: LokiDatasource, request: DataQueryRequ
})
);
}
// Replicate from backend for split queries for now, until we can move query splitting to the backend
// https://github.com/grafana/grafana/blob/main/pkg/tsdb/loki/step.go#L23
function calculateStep(intervalMs: number, range: TimeRange, resolution: number, step: string | undefined) {
// If we can parse step,the we use it
// Otherwise we will calculate step based on interval
const interval_regex = /(-?\d+(?:\.\d+)?)(ms|[Mwdhmsy])/;
if (step?.match(interval_regex)) {
return rangeUtil.intervalToMs(step) * resolution;
}
const newStep = intervalMs * resolution;
const safeStep = Math.round((range.to.valueOf() - range.from.valueOf()) / 11000);
return Math.max(newStep, safeStep);
}
@@ -38,8 +38,7 @@ describe('LokiQueryBuilderOptions', () => {
});
it('can change line limit to valid value', async () => {
const { props } = setup();
props.query.expr = '{foo="bar"}';
const { props } = setup({ expr: '{foo="bar"}' });
await userEvent.click(screen.getByTitle('Click to edit options'));
// Second autosize input is a Line limit
@@ -54,10 +53,9 @@ describe('LokiQueryBuilderOptions', () => {
});
it('does not change line limit to invalid numeric value', async () => {
const { props } = setup();
const { props } = setup({ expr: '{foo="bar"}' });
// We need to start with some value to be able to change it
props.query.maxLines = 10;
props.query.expr = '{foo="bar"}';
await userEvent.click(screen.getByTitle('Click to edit options'));
// Second autosize input is a Line limit
@@ -72,10 +70,9 @@ describe('LokiQueryBuilderOptions', () => {
});
it('does not change line limit to invalid text value', async () => {
const { props } = setup();
const { props } = setup({ expr: '{foo="bar"}' });
// We need to start with some value to be able to change it
props.query.maxLines = 10;
props.query.expr = '{foo="bar"}';
await userEvent.click(screen.getByTitle('Click to edit options'));
// Second autosize input is a Line limit
@@ -88,6 +85,20 @@ describe('LokiQueryBuilderOptions', () => {
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' });
expect(screen.queryByText('Line limit: 20')).not.toBeInTheDocument();
expect(screen.getByText('Type: Range')).toBeInTheDocument();
expect(screen.getByText('Step: 1m')).toBeInTheDocument();
});
});
function setup(queryOverrides: Partial<LokiQuery> = {}) {
@@ -1,3 +1,4 @@
import { trim } from 'lodash';
import React, { useState } from 'react';
import { CoreApp, isValidDuration, SelectableValue } from '@grafana/data';
@@ -61,14 +62,19 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
}
}
let queryType = query.queryType ?? (query.instant ? LokiQueryType.Instant : LokiQueryType.Range);
let showMaxLines = isLogsQuery(query.expr);
function onStepChange(e: React.SyntheticEvent<HTMLInputElement>) {
onChange({ ...query, step: trim(e.currentTarget.value) });
onRunQuery();
}
const queryType = query.queryType ?? (query.instant ? LokiQueryType.Instant : LokiQueryType.Range);
const isLogQuery = isLogsQuery(query.expr);
return (
<EditorRow>
<QueryOptionGroup
title="Options"
collapsedInfo={getCollapsedInfo(query, queryType, showMaxLines, maxLines)}
collapsedInfo={getCollapsedInfo(query, queryType, maxLines, isLogQuery)}
queryStats={queryStats}
>
<EditorField
@@ -86,7 +92,7 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
<EditorField label="Type">
<RadioButtonGroup options={queryTypeOptions} value={queryType} onChange={onQueryTypeChange} />
</EditorField>
{showMaxLines && (
{isLogQuery && (
<EditorField label="Line limit" tooltip="Upper limit for number of log lines returned by query.">
<AutoSizeInput
className="width-4"
@@ -98,18 +104,34 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
/>
</EditorField>
)}
<EditorField
label="Resolution"
tooltip="Sets 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>
{!isLogQuery && (
<>
<EditorField
label="Step"
tooltip="Use the step parameter when making metric queries to Loki. If not filled, Grafana's calculated interval will be used. Example valid values: 1s, 5m, 10h, 1d."
>
<AutoSizeInput
className="width-6"
placeholder={'auto'}
type="string"
defaultValue={query.step ?? ''}
onCommitChange={onStepChange}
/>
</EditorField>
<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>
</>
)}
{config.featureToggles.lokiQuerySplittingConfig && config.featureToggles.lokiQuerySplitting && (
<EditorField
label="Split Duration"
@@ -131,12 +153,7 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
}
);
function getCollapsedInfo(
query: LokiQuery,
queryType: LokiQueryType,
showMaxLines: boolean,
maxLines: number
): string[] {
function getCollapsedInfo(query: LokiQuery, queryType: LokiQueryType, maxLines: number, isLogQuery: boolean): string[] {
const queryTypeLabel = queryTypeOptions.find((x) => x.value === queryType);
const resolutionLabel = RESOLUTION_OPTIONS.find((x) => x.value === (query.resolution ?? 1));
@@ -152,10 +169,20 @@ function getCollapsedInfo(
items.push(`Type: ${queryTypeLabel?.label}`);
if (showMaxLines) {
if (isLogQuery) {
items.push(`Line limit: ${query.maxLines ?? maxLines}`);
}
if (!isLogQuery) {
if (query.step) {
items.push(`Step: ${query.step}`);
}
if (query.resolution) {
items.push(`Resolution: ${resolutionLabel?.label}`);
}
}
return items;
}
@@ -44,7 +44,7 @@ const requests: LokiGroupedRequest[] = [
}),
app: 'explore',
},
partition: partitionTimeRange(true, range, 60000, 1, 24 * 60 * 60 * 1000),
partition: partitionTimeRange(true, range, 60000, 24 * 60 * 60 * 1000),
},
{
request: {
@@ -54,7 +54,7 @@ const requests: LokiGroupedRequest[] = [
}),
app: 'explore',
},
partition: partitionTimeRange(false, range, 60000, 1, 24 * 60 * 60 * 1000),
partition: partitionTimeRange(false, range, 60000, 24 * 60 * 60 * 1000),
},
];