import { css } from '@emotion/css'; import React, { PureComponent, ChangeEvent, FocusEvent } from 'react'; import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data'; import { Switch, Input, InlineFormLabel, stylesFactory } from '@grafana/ui'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { config } from 'app/core/config'; 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), }); }; 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.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
); } renderQueryCachingTTLOption() { const { dataSource, options } = this.props; 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) { return null; } return (
Cache TTL
); } 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. 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; 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()} {this.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
{(timeShift || relativeTime) && (
Hide time info
)}
); } } 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;