[v10.1.x] Loki: Fix validation of step values to also allow e.g. ms values (#73335)

Loki: Fix validation of `step` values to also allow e.g. `ms` values (#73270)

* use go duration validation

* add `isValidGrafanaDuration`

* use `isValidGrafanaDuration`

* improve jsdoc

(cherry picked from commit c006ea18fb)

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
This commit is contained in:
grafana-delivery-bot[bot]
2023-08-16 18:05:38 +02:00
committed by GitHub
co-authored by Sven Grossmann
parent 47ff0a9eb1
commit fc743324cb
4 changed files with 63 additions and 4 deletions
@@ -5,6 +5,7 @@ import {
isValidDuration,
isValidGoDuration,
durationToMilliseconds,
isValidGrafanaDuration,
} from './durationutil';
describe('Duration util', () => {
@@ -68,6 +69,28 @@ describe('Duration util', () => {
});
});
describe('isValidGrafanaDuration', () => {
it('valid duration string returns true', () => {
const durationString = '7y 6M 5w 4d 3h 4m 1s 2ms 3us 5ns';
expect(isValidGrafanaDuration(durationString)).toEqual(true);
});
it('valid float number duration string returns true', () => {
const durationString = '7.1y 6.1M 5.1w 4.1d 3.1h 4.0m 0.1s 2.11ms 0.03us 5.3333ns';
expect(isValidGrafanaDuration(durationString)).toEqual(true);
});
it('invalid duration string returns false', () => {
const durationString = '3M 6v 5b 4m';
expect(isValidGrafanaDuration(durationString)).toEqual(false);
});
it('invalid float number duration string returns false', () => {
const durationString = '3.h -4.0m 0.s 2.ms -0.us 5.ns';
expect(isValidGrafanaDuration(durationString)).toEqual(false);
});
});
describe('durationToMilliseconds', () => {
it('converts a duration to milliseconds', () => {
const duration = { hours: 1, minutes: 30, seconds: 45 };
@@ -99,8 +99,11 @@ export function isValidDate(dateString: string): boolean {
}
/**
* isValidDuration returns true if the given string can be parsed into a valid Duration object, false otherwise
* isValidDuration returns true if the given string can be parsed into a valid `date-fns` `Duration` object, false otherwise
*
* Valid time units are "y", "Y", "years", "M", "months", "w", "W", "weeks", "d", "D", "days", "h", "H", "hours", "m", "minutes", "s", "S", "seconds"
*
* @see https://date-fns.org/v2.30.0/docs/Duration
* @param durationString - string representation of a duration
*
* @public
@@ -127,7 +130,7 @@ export function isValidDuration(durationString: string): boolean {
*
* Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
*
* Go docs: https://pkg.go.dev/time#ParseDuration
* @see https://pkg.go.dev/time#ParseDuration
*
* @param durationString - string representation of a duration
*
@@ -135,6 +138,27 @@ export function isValidDuration(durationString: string): boolean {
*/
export function isValidGoDuration(durationString: string): boolean {
const timeUnits = ['h', 'm', 's', 'ms', 'us', 'µs', 'ns'];
return validateDurationByUnits(durationString, timeUnits);
}
/**
* isValidGrafanaDuration returns `true` if the given string can be parsed into a valid Duration object based on
* the Grafana SDK's gtime.parseDuration, `false` otherwise.
*
* Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h", "d", "w", "M", "y".
*
* @see https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend/gtime#ParseDuration
*
* @param durationString - string representation of a duration
*
* @internal
*/
export function isValidGrafanaDuration(durationString: string): boolean {
const timeUnits = ['y', 'M', 'w', 'd', 'h', 'm', 's', 'ms', 'us', 'µs', 'ns'];
return validateDurationByUnits(durationString, timeUnits);
}
function validateDurationByUnits(durationString: string, timeUnits: string[]): boolean {
for (const value of durationString.trim().split(' ')) {
const match = value.match(/([0-9]*[.]?[0-9]+)(.+)/);
if (match === null || match.length !== 3) {
@@ -140,6 +140,18 @@ describe('LokiQueryBuilderOptions', () => {
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(screen.queryByText(/Invalid step/)).not.toBeInTheDocument();
});
it('does not shows 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 shows 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();
});
});
function setup(queryOverrides: Partial<LokiQuery> = {}) {
@@ -1,7 +1,7 @@
import { trim } from 'lodash';
import React, { useMemo, useState } from 'react';
import { CoreApp, isValidDuration, SelectableValue } from '@grafana/data';
import { CoreApp, isValidDuration, isValidGrafanaDuration, SelectableValue } from '@grafana/data';
import { EditorField, EditorRow } from '@grafana/experimental';
import { config, reportInteraction } from '@grafana/runtime';
import { Alert, AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui';
@@ -71,7 +71,7 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
const isLogQuery = isLogsQuery(query.expr);
const isValidStep = useMemo(() => {
if (!query.step || isValidDuration(query.step) || !isNaN(Number(query.step))) {
if (!query.step || isValidGrafanaDuration(query.step) || !isNaN(Number(query.step))) {
return true;
}
return false;