// Libraries import React, { PureComponent, ChangeEvent, FocusEvent } from 'react'; // Utils import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data'; // Components import { EventsWithValidation, LegacyInputStatus, LegacyForms, ValidationEvents, InlineFormLabel, stylesFactory, } from '@grafana/ui'; const { Switch, Input } = LegacyForms; // Types import { PanelModel } from '../state'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { config } from 'app/core/config'; import { css } from 'emotion'; const timeRangeValidationEvents: ValidationEvents = { [EventsWithValidation.onBlur]: [ { rule: value => { if (!value) { return true; } return rangeUtil.isValidTimeSpan(value); }, errorMessage: 'Not a valid timespan', }, ], }; const emptyToNull = (value: string) => { return value === '' ? null : value; }; interface Props { panel: PanelModel; dataSource: DataSourceApi; data: PanelData; } interface State { relativeTime: string; timeShift: string; cacheTimeout: string; maxDataPoints: number | string; interval: string; hideTimeOverride: boolean; isOpen: boolean; } export class QueryOptions extends PureComponent { constructor(props: Props) { super(props); this.state = { relativeTime: props.panel.timeFrom || '', timeShift: props.panel.timeShift || '', cacheTimeout: props.panel.cacheTimeout || '', maxDataPoints: props.panel.maxDataPoints ?? '', interval: props.panel.interval || '', hideTimeOverride: props.panel.hideTimeOverride || false, isOpen: false, }; } onRelativeTimeChange = (event: ChangeEvent) => { this.setState({ relativeTime: event.target.value, }); }; onTimeShiftChange = (event: ChangeEvent) => { this.setState({ timeShift: event.target.value, }); }; onOverrideTime = (event: FocusEvent, status: LegacyInputStatus) => { const { value } = event.target; const { panel } = this.props; const emptyToNullValue = emptyToNull(value); if (status === LegacyInputStatus.Valid && panel.timeFrom !== emptyToNullValue) { panel.timeFrom = emptyToNullValue; panel.refresh(); } }; onTimeShift = (event: FocusEvent, status: LegacyInputStatus) => { const { value } = event.target; const { panel } = this.props; const emptyToNullValue = emptyToNull(value); if (status === LegacyInputStatus.Valid && panel.timeShift !== emptyToNullValue) { panel.timeShift = emptyToNullValue; panel.refresh(); } }; onToggleTimeOverride = () => { const { panel } = this.props; this.setState({ hideTimeOverride: !this.state.hideTimeOverride }, () => { panel.hideTimeOverride = this.state.hideTimeOverride; panel.refresh(); }); }; onDataSourceOptionBlur = (panelKey: string) => () => { const { panel } = this.props; // @ts-ignore panel[panelKey] = this.state[panelKey]; panel.refresh(); }; onDataSourceOptionChange = (panelKey: string) => (event: ChangeEvent) => { this.setState({ ...this.state, [panelKey]: event.target.value }); }; onMaxDataPointsBlur = () => { const { panel } = this.props; const maxDataPoints = parseInt(this.state.maxDataPoints as string, 10); if (isNaN(maxDataPoints)) { delete panel.maxDataPoints; } else { panel.maxDataPoints = maxDataPoints; } panel.refresh(); }; renderCacheTimeoutOption() { const { dataSource } = this.props; const { cacheTimeout } = this.state; 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 } = this.props; const { maxDataPoints } = this.state; const realMd = data.request?.maxDataPoints; const isAuto = maxDataPoints === ''; 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 } = this.props; const { interval } = this.state; 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 } = this.props; const { isOpen, maxDataPoints, interval } = this.state; if (isOpen) { return undefined; } let mdDesc = maxDataPoints; if (maxDataPoints === '' && data.request) { mdDesc = `auto = ${data.request.maxDataPoints}`; } let intervalDesc = interval; if (data.request) { intervalDesc = `${data.request.interval}`; } return ( <> {
MD = {mdDesc}
} {
Interval = {intervalDesc}
} ); } render() { const { hideTimeOverride } = this.state; const { relativeTime, timeShift, isOpen } = this.state; const styles = getStyles(); return ( {this.renderMaxDataPointsOption()} {this.renderIntervalOption()} {this.renderCacheTimeoutOption()}
Relative time
Time shift
{(timeShift || relativeTime) && (
)}
); } } 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;