// Libraries import React, { PureComponent, ChangeEvent, FocusEvent } from 'react'; // Utils import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data'; // Components import { Switch, Input, InlineField, InlineFormLabel, stylesFactory } from '@grafana/ui'; // Types import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { config } from 'app/core/config'; import { css } from '@emotion/css'; import { QueryGroupOptions } from 'app/types'; interface Props { options: QueryGroupOptions; dataSource: DataSourceApi; data: PanelData; onChange: (options: QueryGroupOptions) => void; } interface State { timeRangeFrom: string; timeRangeShift: string; timeRangeHide: boolean; isOpen: boolean; relativeTimeIsValid: boolean; timeShiftIsValid: boolean; } export class QueryGroupOptionsEditor extends PureComponent { constructor(props: Props) { super(props); const { options } = props; this.state = { timeRangeFrom: options.timeRange?.from || '', timeRangeShift: options.timeRange?.shift || '', timeRangeHide: options.timeRange?.hide ?? false, isOpen: false, relativeTimeIsValid: true, timeShiftIsValid: true, }; } onRelativeTimeChange = (event: ChangeEvent) => { this.setState({ timeRangeFrom: event.target.value, }); }; onTimeShiftChange = (event: ChangeEvent) => { this.setState({ timeRangeShift: event.target.value, }); }; onOverrideTime = (event: FocusEvent) => { const { options, onChange } = this.props; const newValue = emptyToNull(event.target.value); const isValid = timeRangeValidation(newValue); if (isValid && options.timeRange?.from !== newValue) { onChange({ ...options, timeRange: { ...(options.timeRange ?? {}), from: newValue, }, }); } this.setState({ relativeTimeIsValid: isValid }); }; 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; onChange({ ...options, cacheTimeout: emptyToNull(event.target.value), }); }; onMaxDataPointsBlur = (event: ChangeEvent) => { const { options, onChange } = this.props; let maxDataPoints: number | null = parseInt(event.target.value as string, 10); if (isNaN(maxDataPoints) || maxDataPoints === 0) { maxDataPoints = null; } if (maxDataPoints !== options.maxDataPoints) { onChange({ ...options, maxDataPoints, }); } }; onMinIntervalBlur = (event: ChangeEvent) => { const { options, onChange } = this.props; const minInterval = emptyToNull(event.target.value); if (minInterval !== options.minInterval) { onChange({ ...options, minInterval, }); } }; renderCacheTimeoutOption() { const { dataSource, options } = this.props; 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.`; if (!dataSource.meta.queryOptions?.cacheTimeout) { return null; } return (
Cache timeout
); } renderMaxDataPointsOption() { const { data, options } = this.props; const realMd = data.request?.maxDataPoints; const value = options.maxDataPoints ?? ''; const isAuto = value === ''; 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
)}
); } renderIntervalOption() { const { data, dataSource, options } = this.props; const realInterval = data.request?.interval; const minIntervalOnDs = dataSource.interval ?? 'No limit'; return ( <>
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 } > Interval {realInterval}
=
Max data points / time range
); } 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; if (isOpen) { return undefined; } let mdDesc = options.maxDataPoints ?? ''; if (mdDesc === '' && data.request) { mdDesc = `auto = ${data.request.maxDataPoints}`; } let intervalDesc = options.minInterval; if (data.request) { intervalDesc = `${data.request.interval}`; } return ( <> {
MD = {mdDesc}
} {
Interval = {intervalDesc}
} ); } render() { const { timeRangeHide: hideTimeOverride, relativeTimeIsValid, timeShiftIsValid } = this.state; const { timeRangeFrom: relativeTime, timeRangeShift: timeShift, isOpen } = this.state; const styles = getStyles(); return ( {this.renderMaxDataPointsOption()} {this.renderIntervalOption()} {this.renderCacheTimeoutOption()}
Relative time
Time shift
{(timeShift || relativeTime) && (
)}
); } } const timeRangeValidation = (value: string | null) => { if (!value) { return true; } return rangeUtil.isValidTimeSpan(value); }; const emptyToNull = (value: string) => { return value === '' ? null : value; }; const getStyles = stylesFactory(() => { const { theme } = config; return { collapsedText: css` margin-left: ${theme.spacing.md}; font-size: ${theme.typography.size.sm}; color: ${theme.colors.textWeak}; `, }; }); type StylesType = ReturnType;