diff --git a/.betterer.results b/.betterer.results
index 31d1f23e1c8..3b6f2de23a9 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -4770,8 +4770,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "35"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "36"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "37"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "38"],
- [0, 0, 0, "Styles should be written using objects.", "39"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "38"]
],
"public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
@@ -7310,24 +7309,6 @@ exports[`no gf-form usage`] = {
"public/app/features/query/components/QueryEditorRow.tsx:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
- "public/app/features/query/components/QueryGroupOptions.tsx:5381": [
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
- [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
- ],
"public/app/features/variables/adhoc/picker/AdHocFilter.tsx:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
diff --git a/public/app/features/query/components/QueryGroupOptions.tsx b/public/app/features/query/components/QueryGroupOptions.tsx
index 8fc36b47e1e..c949e1767b2 100644
--- a/public/app/features/query/components/QueryGroupOptions.tsx
+++ b/public/app/features/query/components/QueryGroupOptions.tsx
@@ -1,11 +1,9 @@
-import { css } from '@emotion/css';
-import { PureComponent, ChangeEvent, FocusEvent } from 'react';
-import * as React from 'react';
+import { css, cx } from '@emotion/css';
+import React, { useState, ChangeEvent, FocusEvent, useCallback } from 'react';
-import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data';
-import { Input, InlineFormLabel, stylesFactory, InlineFieldRow, InlineSwitch } from '@grafana/ui';
+import { rangeUtil, PanelData, DataSourceApi, GrafanaTheme2 } from '@grafana/data';
+import { Input, InlineSwitch, useStyles2, InlineLabel } from '@grafana/ui';
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
-import { config } from 'app/core/config';
import { QueryGroupOptions } from 'app/types';
interface Props {
@@ -15,149 +13,142 @@ interface Props {
onChange: (options: QueryGroupOptions) => void;
}
-interface State {
- timeRangeFrom: string;
- timeRangeShift: string;
- timeRangeHide: boolean;
- isOpen: boolean;
- relativeTimeIsValid: boolean;
- timeShiftIsValid: boolean;
-}
+export const QueryGroupOptionsEditor = React.memo(({ options, dataSource, data, onChange }: Props) => {
+ const [timeRangeFrom, setTimeRangeFrom] = useState(options.timeRange?.from || '');
+ const [timeRangeShift, setTimeRangeShift] = useState(options.timeRange?.shift || '');
+ const [timeRangeHide, setTimeRangeHide] = useState(options.timeRange?.hide ?? false);
+ const [isOpen, setIsOpen] = useState(false);
+ const [relativeTimeIsValid, setRelativeTimeIsValid] = useState(true);
+ const [timeShiftIsValid, setTimeShiftIsValid] = useState(true);
-export class QueryGroupOptionsEditor extends PureComponent {
- constructor(props: Props) {
- super(props);
+ const styles = useStyles2(getStyles);
- const { options } = props;
+ const onRelativeTimeChange = useCallback((event: ChangeEvent) => {
+ setTimeRangeFrom(event.target.value);
+ }, []);
- this.state = {
- timeRangeFrom: options.timeRange?.from || '',
- timeRangeShift: options.timeRange?.shift || '',
- timeRangeHide: options.timeRange?.hide ?? false,
- isOpen: false,
- relativeTimeIsValid: true,
- timeShiftIsValid: true,
- };
- }
+ const onTimeShiftChange = useCallback((event: ChangeEvent) => {
+ setTimeRangeShift(event.target.value);
+ }, []);
- onRelativeTimeChange = (event: ChangeEvent) => {
- this.setState({
- timeRangeFrom: event.target.value,
- });
- };
+ const onOverrideTime = useCallback(
+ (event: FocusEvent) => {
+ const newValue = emptyToNull(event.target.value);
+ const isValid = timeRangeValidation(newValue);
- onTimeShiftChange = (event: ChangeEvent) => {
- this.setState({
- timeRangeShift: event.target.value,
- });
- };
+ if (isValid && options.timeRange?.from !== newValue) {
+ onChange({
+ ...options,
+ timeRange: {
+ ...(options.timeRange ?? {}),
+ from: newValue,
+ },
+ });
+ }
- onOverrideTime = (event: FocusEvent) => {
- const { options, onChange } = this.props;
+ setRelativeTimeIsValid(isValid);
+ },
+ [onChange, options]
+ );
- const newValue = emptyToNull(event.target.value);
- const isValid = timeRangeValidation(newValue);
+ const onTimeShift = useCallback(
+ (event: FocusEvent) => {
+ const newValue = emptyToNull(event.target.value);
+ const isValid = timeRangeValidation(newValue);
- if (isValid && options.timeRange?.from !== newValue) {
- onChange({
- ...options,
- timeRange: {
- ...(options.timeRange ?? {}),
- from: newValue,
- },
- });
- }
+ if (isValid && options.timeRange?.shift !== newValue) {
+ onChange({
+ ...options,
+ timeRange: {
+ ...(options.timeRange ?? {}),
+ shift: newValue,
+ },
+ });
+ }
- this.setState({ relativeTimeIsValid: isValid });
- };
+ setTimeShiftIsValid(isValid);
+ },
+ [onChange, options]
+ );
- onTimeShift = (event: FocusEvent) => {
- const { options, onChange } = this.props;
-
- const newValue = emptyToNull(event.target.value);
- const isValid = timeRangeValidation(newValue);
-
- if (isValid && options.timeRange?.shift !== newValue) {
- onChange({
- ...options,
- timeRange: {
- ...(options.timeRange ?? {}),
- shift: newValue,
- },
- });
- }
-
- this.setState({ timeShiftIsValid: isValid });
- };
-
- onToggleTimeOverride = () => {
- const { onChange, options } = this.props;
-
- this.setState({ timeRangeHide: !this.state.timeRangeHide }, () => {
- onChange({
- ...options,
- timeRange: {
- ...(options.timeRange ?? {}),
- hide: this.state.timeRangeHide,
- },
- });
- });
- };
-
- onCacheTimeoutBlur = (event: ChangeEvent) => {
- const { options, onChange } = this.props;
+ const onToggleTimeOverride = useCallback(() => {
+ const newTimeRangeHide = !timeRangeHide;
+ setTimeRangeHide(newTimeRangeHide);
onChange({
...options,
- cacheTimeout: emptyToNull(event.target.value),
+ timeRange: {
+ ...(options.timeRange ?? {}),
+ hide: newTimeRangeHide,
+ },
});
- };
+ }, [onChange, options, timeRangeHide]);
- onQueryCachingTTLBlur = (event: ChangeEvent) => {
- const { options, onChange } = this.props;
-
- let ttl: number | null = parseInt(event.target.value, 10);
-
- if (isNaN(ttl) || ttl === 0) {
- ttl = null;
- }
-
- onChange({
- ...options,
- queryCachingTTL: ttl,
- });
- };
-
- onMaxDataPointsBlur = (event: ChangeEvent) => {
- const { options, onChange } = this.props;
-
- let maxDataPoints: number | null = parseInt(event.currentTarget.value, 10);
-
- if (isNaN(maxDataPoints) || maxDataPoints === 0) {
- maxDataPoints = null;
- }
-
- if (maxDataPoints !== options.maxDataPoints) {
+ const onCacheTimeoutBlur = useCallback(
+ (event: ChangeEvent) => {
onChange({
...options,
- maxDataPoints,
+ cacheTimeout: emptyToNull(event.target.value),
});
- }
- };
+ },
+ [onChange, options]
+ );
+
+ const onQueryCachingTTLBlur = useCallback(
+ (event: ChangeEvent) => {
+ let ttl: number | null = parseInt(event.target.value, 10);
+
+ if (isNaN(ttl) || ttl === 0) {
+ ttl = null;
+ }
- onMinIntervalBlur = (event: ChangeEvent) => {
- const { options, onChange } = this.props;
- const minInterval = emptyToNull(event.target.value);
- if (minInterval !== options.minInterval) {
onChange({
...options,
- minInterval,
+ queryCachingTTL: ttl,
});
- }
- };
+ },
+ [onChange, options]
+ );
- renderCacheTimeoutOption() {
- const { dataSource, options } = this.props;
+ const onMaxDataPointsBlur = useCallback(
+ (event: ChangeEvent) => {
+ let maxDataPoints: number | null = parseInt(event.currentTarget.value, 10);
+ if (isNaN(maxDataPoints) || maxDataPoints === 0) {
+ maxDataPoints = null;
+ }
+
+ if (maxDataPoints !== options.maxDataPoints) {
+ onChange({
+ ...options,
+ maxDataPoints,
+ });
+ }
+ },
+ [onChange, options]
+ );
+
+ const onMinIntervalBlur = useCallback(
+ (event: ChangeEvent) => {
+ const minInterval = emptyToNull(event.target.value);
+ if (minInterval !== options.minInterval) {
+ onChange({
+ ...options,
+ minInterval,
+ });
+ }
+ },
+ [onChange, options]
+ );
+
+ const onOpenOptions = useCallback(() => {
+ setIsOpen(true);
+ }, []);
+
+ const onCloseOptions = useCallback(() => {
+ setIsOpen(false);
+ }, []);
+
+ const renderCacheTimeoutOption = () => {
const tooltip = `If your time series store has a query cache this option can override the default cache timeout. Specify a
numeric value in seconds.`;
@@ -166,27 +157,23 @@ export class QueryGroupOptionsEditor extends PureComponent {
}
return (
-
-
-
- Cache timeout
-
-
-
-
+ <>
+
+ Cache timeout
+
+
+ >
);
- }
-
- renderQueryCachingTTLOption() {
- const { dataSource, options } = this.props;
+ };
+ const renderQueryCachingTTLOption = () => {
const tooltip = `Cache time-to-live: How long results from this queries in this panel will be cached, in milliseconds. Defaults to the TTL in the caching configuration for this datasource.`;
if (!dataSource.cachingConfig?.enabled) {
@@ -194,129 +181,101 @@ export class QueryGroupOptionsEditor extends PureComponent {
}
return (
-
-
- The maximum data points per series. Used directly by some data sources and used in calculation of auto
- interval. With streaming data this value is used for the rolling buffer.
- >
- }
- >
- Max data points
-
-
- {isAuto && (
+ <>
+
-
=
-
Width of panel
+ The maximum data points per series. Used directly by some data sources and used in calculation of auto
+ interval. With streaming data this value is used for the rolling buffer.
>
- )}
-
-
- A lower limit for the interval. Recommended to be set to write frequency, for example 1m{' '}
- if your data is written every minute. Default value can be set in data source settings for most data
- sources.
- >
- }
- >
- Min interval
-
-
-
-
-
-
-
- The evaluated interval that is sent to data source and is used in $__interval and{' '}
- $__interval_ms. This value is not exactly equal to{' '}
- Time range / max data points, it will approximate a series of magic number.
- >
- }
- >
- Interval
-
- {realInterval}
-
=
-
Time range / max data points
-
-
+
+ A lower limit for the interval. Recommended to be set to write frequency, for example 1m if
+ your data is written every minute. Default value can be set in data source settings for most data sources.
+ >
+ }
+ htmlFor="min-interval-input"
+ >
+ Min interval
+
+
+
+ The evaluated interval that is sent to data source and is used in $__interval and{' '}
+ $__interval_ms. This value is not exactly equal to Time range / max data points,
+ it will approximate a series of magic number.
+ >
+ }
+ >
+ Interval
+
+ {realInterval}
+ =
+ Time range / max data points
>
);
- }
-
- onOpenOptions = () => {
- this.setState({ isOpen: true });
};
- onCloseOptions = () => {
- this.setState({ isOpen: false });
- };
-
- renderCollapsedText(styles: StylesType): React.ReactNode | undefined {
- const { data, options } = this.props;
- const { isOpen } = this.state;
-
+ const renderCollapsedText = (): React.ReactNode | undefined => {
if (isOpen) {
return undefined;
}
@@ -326,120 +285,135 @@ export class QueryGroupOptionsEditor extends PureComponent {
mdDesc = `auto = ${data.request.maxDataPoints}`;
}
- let intervalDesc = options.minInterval;
- if (data.request) {
- intervalDesc = `${data.request.interval}`;
- }
+ const intervalDesc = data.request?.interval ?? options.minInterval;
return (
<>
- {
-
- Overrides the relative time range for individual panels, which causes them to be different than what is
- selected in the dashboard time picker in the top-right corner of the dashboard. For example to configure
- the Last 5 minutes the Relative time should be now-5m and 5m, or variables
- like $_relativeTime.
- >
- }
- >
- Relative time
-
-
-
-
-
-
- Overrides the time range for individual panels by shifting its start and end relative to the time
- picker. For example to configure the Last 1h the Time shift should be now-1h and{' '}
- 1h, or variables like $_timeShift.
- >
- }
- >
- Time shift
-
-
-
+ {renderMaxDataPointsOption()}
+ {renderIntervalOption()}
+ {renderCacheTimeoutOption()}
+ {renderQueryCachingTTLOption()}
+
+
+ Overrides the relative time range for individual panels, which causes them to be different than what is
+ selected in the dashboard time picker in the top-right corner of the dashboard. For example to configure
+ the Last 5 minutes the Relative time should be now-5m and 5m, or variables like{' '}
+ $_relativeTime.
+ >
+ }
+ >
+ Relative time
+
+
+
+ Overrides the time range for individual panels by shifting its start and end relative to the time picker.
+ For example to configure the Last 1h the Time shift should be now-1h and 1h, or
+ variables like $_timeShift.
+ >
+ }
+ >
+ Time shift
+
+
+ {(timeRangeShift || timeRangeFrom) && (
+ <>
+
+ Hide time info
+
+
+ >
+ )}
+