mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
MaxDataPoints: Now used in interval calculation for all data sources (#23915)
* MaxDataPoints: Now enabled for all * Updates to code and test * Moved the panel query inspector * PaneQueryRunner: Simplify logic and only take in maxDataPoints not width
This commit is contained in:
parent
32492dd650
commit
0742dbc9be
@ -109,7 +109,7 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
`,
|
||||
content: css`
|
||||
margin-top: ${theme.spacing.inlineFormMargin};
|
||||
margin-left: ${theme.spacing.xl};
|
||||
margin-left: ${theme.spacing.lg};
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
@ -200,8 +200,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
timezone: this.props.dashboard.getTimezone(),
|
||||
timeRange: timeData.timeRange,
|
||||
timeInfo: timeData.timeInfo,
|
||||
widthPixels: width,
|
||||
maxDataPoints: panel.maxDataPoints,
|
||||
maxDataPoints: panel.maxDataPoints || width,
|
||||
minInterval: panel.interval,
|
||||
scopedVars: panel.scopedVars,
|
||||
cacheTimeout: panel.cacheTimeout,
|
||||
|
@ -1,33 +0,0 @@
|
||||
import React, { FC, ChangeEvent } from 'react';
|
||||
import { InlineFormLabel, LegacyForms } from '@grafana/ui';
|
||||
const { Input } = LegacyForms;
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
name: string;
|
||||
value: string;
|
||||
onBlur: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
tooltipInfo?: any;
|
||||
}
|
||||
|
||||
export const DataSourceOption: FC<Props> = ({ label, placeholder, name, value, onBlur, onChange, tooltipInfo }) => {
|
||||
return (
|
||||
<div className="gf-form gf-form--flex-end">
|
||||
<InlineFormLabel width={9} tooltip={tooltipInfo}>
|
||||
{label}
|
||||
</InlineFormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
className="gf-form-input width-6"
|
||||
placeholder={placeholder}
|
||||
name={name}
|
||||
spellCheck={false}
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -172,7 +172,15 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
<DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} />
|
||||
</div>
|
||||
<div className={styles.dataSourceRowItem}>
|
||||
<Button variant="secondary" icon="info-circle" title="Open data source help" onClick={this.onOpenHelp} />
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon="question-circle"
|
||||
title="Open data source help"
|
||||
onClick={this.onOpenHelp}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.dataSourceRowItemOptions}>
|
||||
<QueryOptions panel={panel} datasource={currentDS} data={data} />
|
||||
</div>
|
||||
<div className={styles.dataSourceRowItem}>
|
||||
<Button
|
||||
@ -183,9 +191,6 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
Query inspector
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.dataSourceRowItemOptions}>
|
||||
<QueryOptions panel={panel} datasource={currentDS} data={data} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -327,6 +332,7 @@ const getStyles = stylesFactory(() => {
|
||||
`,
|
||||
dataSourceRowItemOptions: css`
|
||||
flex-grow: 1;
|
||||
margin-right: ${theme.spacing.inlineFormMargin};
|
||||
`,
|
||||
queriesWrapper: css`
|
||||
padding-bottom: 16px;
|
||||
|
@ -13,8 +13,7 @@ import {
|
||||
InlineFormLabel,
|
||||
stylesFactory,
|
||||
} from '@grafana/ui';
|
||||
import { DataSourceOption } from './DataSourceOption';
|
||||
const { Input, Switch } = LegacyForms;
|
||||
const { Switch, Input } = LegacyForms;
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../state';
|
||||
@ -57,46 +56,6 @@ interface State {
|
||||
}
|
||||
|
||||
export class QueryOptions extends PureComponent<Props, State> {
|
||||
allOptions: any = {
|
||||
cacheTimeout: {
|
||||
label: 'Cache timeout',
|
||||
placeholder: '60',
|
||||
name: 'cacheTimeout',
|
||||
tooltipInfo: (
|
||||
<>
|
||||
If your time series store has a query cache this option can override the default cache timeout. Specify a
|
||||
numeric value in seconds.
|
||||
</>
|
||||
),
|
||||
},
|
||||
maxDataPoints: {
|
||||
label: 'Max data points',
|
||||
placeholder: 'auto',
|
||||
name: 'maxDataPoints',
|
||||
tooltipInfo: (
|
||||
<>
|
||||
The maximum data points the query should return. For graphs this is automatically set to one data point per
|
||||
pixel. For some data sources this can also be capped in the datasource settings page. With streaming data,
|
||||
this value is used for the rolling buffer.
|
||||
</>
|
||||
),
|
||||
},
|
||||
minInterval: {
|
||||
label: 'Min time interval',
|
||||
placeholder: '0',
|
||||
name: 'minInterval',
|
||||
panelKey: 'interval',
|
||||
tooltipInfo: (
|
||||
<>
|
||||
A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example{' '}
|
||||
<code>1m</code> if your data is written every minute. Access auto interval via variable{' '}
|
||||
<code>$__interval</code> for time range string and <code>$__interval_ms</code> for numeric variable that can
|
||||
be used in math expressions.
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
@ -127,6 +86,7 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
const { value } = event.target;
|
||||
const { panel } = this.props;
|
||||
const emptyToNullValue = emptyToNull(value);
|
||||
|
||||
if (status === LegacyInputStatus.Valid && panel.timeFrom !== emptyToNullValue) {
|
||||
panel.timeFrom = emptyToNullValue;
|
||||
panel.refresh();
|
||||
@ -137,6 +97,7 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
const { value } = event.target;
|
||||
const { panel } = this.props;
|
||||
const emptyToNullValue = emptyToNull(value);
|
||||
|
||||
if (status === LegacyInputStatus.Valid && panel.timeShift !== emptyToNullValue) {
|
||||
panel.timeShift = emptyToNullValue;
|
||||
panel.refresh();
|
||||
@ -163,35 +124,132 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
this.setState({ ...this.state, [panelKey]: event.target.value });
|
||||
};
|
||||
|
||||
/**
|
||||
* Show options for any value that is set, or values that the
|
||||
* current datasource says it will use
|
||||
*/
|
||||
renderOptions = () => {
|
||||
renderCacheTimeoutOption() {
|
||||
const { datasource } = this.props;
|
||||
const queryOptions: any = datasource.meta.queryOptions || {};
|
||||
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.`;
|
||||
|
||||
return Object.keys(this.allOptions).map(key => {
|
||||
const options = this.allOptions[key];
|
||||
const panelKey = options.panelKey || key;
|
||||
if (!datasource.meta.queryOptions?.maxDataPoints) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const value = this.state[panelKey];
|
||||
|
||||
if (queryOptions[key]) {
|
||||
return (
|
||||
<DataSourceOption
|
||||
key={key}
|
||||
{...options}
|
||||
onChange={this.onDataSourceOptionChange(panelKey)}
|
||||
onBlur={this.onDataSourceOptionBlur(panelKey)}
|
||||
value={value}
|
||||
return (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel width={9} tooltip={tooltip}>
|
||||
Cache timeout
|
||||
</InlineFormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
className="width-6"
|
||||
placeholder="60"
|
||||
name={name}
|
||||
spellCheck={false}
|
||||
onBlur={this.onDataSourceOptionBlur('maxDataPoints')}
|
||||
onChange={this.onDataSourceOptionChange('maxDataPoints')}
|
||||
value={cacheTimeout}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null; // nothing to render
|
||||
});
|
||||
};
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderMaxDataPointsOption() {
|
||||
const { data } = this.props;
|
||||
const { maxDataPoints } = this.state;
|
||||
const realMd = data.request?.maxDataPoints;
|
||||
const isAuto = maxDataPoints === '';
|
||||
|
||||
return (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
width={9}
|
||||
tooltip={
|
||||
<>
|
||||
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
|
||||
</InlineFormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
className="width-6"
|
||||
placeholder={`${realMd}`}
|
||||
name={name}
|
||||
spellCheck={false}
|
||||
onBlur={this.onDataSourceOptionBlur('maxDataPoints')}
|
||||
onChange={this.onDataSourceOptionChange('maxDataPoints')}
|
||||
value={maxDataPoints}
|
||||
/>
|
||||
{isAuto && (
|
||||
<>
|
||||
<div className="gf-form-label query-segment-operator">=</div>
|
||||
<div className="gf-form-label">Width of panel</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderIntervalOption() {
|
||||
const { data } = this.props;
|
||||
const { interval } = this.state;
|
||||
const realInterval = data.request?.interval;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
width={9}
|
||||
tooltip={
|
||||
<>
|
||||
A lower limit for the interval. Recommended to be set to write frequency, for example <code>1m</code>{' '}
|
||||
if your data is written every minute. Default value can be set in data source settings for most data
|
||||
sources.
|
||||
</>
|
||||
}
|
||||
>
|
||||
Min interval
|
||||
</InlineFormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
className="width-6"
|
||||
placeholder={`${realInterval}`}
|
||||
name={name}
|
||||
spellCheck={false}
|
||||
onBlur={this.onDataSourceOptionBlur('interval')}
|
||||
onChange={this.onDataSourceOptionChange('interval')}
|
||||
value={interval}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
width={9}
|
||||
tooltip={
|
||||
<>
|
||||
The evaluated Interval that is sent to data source and is used in <code>$__interval</code> and{' '}
|
||||
<code>$__interval_ms</code>
|
||||
</>
|
||||
}
|
||||
>
|
||||
Interval
|
||||
</InlineFormLabel>
|
||||
<InlineFormLabel width={6}>{realInterval}</InlineFormLabel>
|
||||
<div className="gf-form-label query-segment-operator">=</div>
|
||||
<div className="gf-form-label">Max data points / time range</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
onOpenOptions = () => {
|
||||
this.setState({ isOpen: true });
|
||||
@ -215,8 +273,8 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
let intervalDesc = interval;
|
||||
if (intervalDesc === '' && data.request) {
|
||||
intervalDesc = `auto = ${data.request.interval}`;
|
||||
if (data.request) {
|
||||
intervalDesc = `${data.request.interval}`;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -240,7 +298,9 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
onOpen={this.onOpenOptions}
|
||||
onClose={this.onCloseOptions}
|
||||
>
|
||||
{this.renderOptions()}
|
||||
{this.renderMaxDataPointsOption()}
|
||||
{this.renderIntervalOption()}
|
||||
{this.renderCacheTimeoutOption()}
|
||||
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel width={9}>Relative time</InlineFormLabel>
|
||||
|
@ -31,7 +31,6 @@ interface ScenarioContext {
|
||||
|
||||
// Options used in setup
|
||||
maxDataPoints?: number | null;
|
||||
widthPixels: number;
|
||||
dsInterval?: string;
|
||||
minInterval?: string;
|
||||
scopedVars: ScopedVars;
|
||||
@ -53,7 +52,7 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn
|
||||
getTransformations: () => undefined,
|
||||
};
|
||||
const ctx: ScenarioContext = {
|
||||
widthPixels: 200,
|
||||
maxDataPoints: 200,
|
||||
scopedVars: {
|
||||
server: { text: 'Server1', value: 'server-1' },
|
||||
},
|
||||
@ -93,12 +92,11 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn
|
||||
datasource,
|
||||
scopedVars: ctx.scopedVars,
|
||||
minInterval: ctx.minInterval,
|
||||
widthPixels: ctx.widthPixels,
|
||||
maxDataPoints: ctx.maxDataPoints,
|
||||
timeRange: {
|
||||
from: grafanaData.dateTime().subtract(1, 'days'),
|
||||
to: grafanaData.dateTime(),
|
||||
raw: { from: '1h', to: 'now' },
|
||||
raw: { from: '1d', to: 'now' },
|
||||
},
|
||||
panelId: 1,
|
||||
queries: [{ refId: 'A', test: 1 }],
|
||||
@ -137,10 +135,9 @@ describe('PanelQueryRunner', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describeQueryRunnerScenario('with no maxDataPoints or minInterval', ctx => {
|
||||
describeQueryRunnerScenario('with maxDataPoints', ctx => {
|
||||
ctx.setup(() => {
|
||||
ctx.maxDataPoints = null;
|
||||
ctx.widthPixels = 200;
|
||||
ctx.maxDataPoints = 200;
|
||||
});
|
||||
|
||||
it('should return data', async () => {
|
||||
@ -163,7 +160,7 @@ describe('PanelQueryRunner', () => {
|
||||
|
||||
describeQueryRunnerScenario('with no panel min interval but datasource min interval', ctx => {
|
||||
ctx.setup(() => {
|
||||
ctx.widthPixels = 20000;
|
||||
ctx.maxDataPoints = 20000;
|
||||
ctx.dsInterval = '15s';
|
||||
});
|
||||
|
||||
@ -174,7 +171,7 @@ describe('PanelQueryRunner', () => {
|
||||
|
||||
describeQueryRunnerScenario('with panel min interval and data source min interval', ctx => {
|
||||
ctx.setup(() => {
|
||||
ctx.widthPixels = 20000;
|
||||
ctx.maxDataPoints = 20000;
|
||||
ctx.dsInterval = '15s';
|
||||
ctx.minInterval = '30s';
|
||||
});
|
||||
@ -192,6 +189,10 @@ describe('PanelQueryRunner', () => {
|
||||
it('should pass maxDataPoints if specified', async () => {
|
||||
expect(ctx.queryCalledWith?.maxDataPoints).toBe(10);
|
||||
});
|
||||
|
||||
it('should use instead of width to calculate interval', async () => {
|
||||
expect(ctx.queryCalledWith?.interval).toBe('2h');
|
||||
});
|
||||
});
|
||||
|
||||
describeQueryRunnerScenario(
|
||||
|
@ -38,8 +38,7 @@ export interface QueryRunnerOptions<
|
||||
timezone?: string;
|
||||
timeRange: TimeRange;
|
||||
timeInfo?: string; // String description of time range for display
|
||||
widthPixels: number;
|
||||
maxDataPoints: number | undefined | null;
|
||||
maxDataPoints: number;
|
||||
minInterval: string | undefined | null;
|
||||
scopedVars?: ScopedVars;
|
||||
cacheTimeout?: string;
|
||||
@ -115,7 +114,6 @@ export class PanelQueryRunner {
|
||||
timeRange,
|
||||
timeInfo,
|
||||
cacheTimeout,
|
||||
widthPixels,
|
||||
maxDataPoints,
|
||||
scopedVars,
|
||||
minInterval,
|
||||
@ -139,7 +137,7 @@ export class PanelQueryRunner {
|
||||
interval: '',
|
||||
intervalMs: 0,
|
||||
targets: cloneDeep(queries),
|
||||
maxDataPoints: maxDataPoints || widthPixels,
|
||||
maxDataPoints: maxDataPoints,
|
||||
scopedVars: scopedVars || {},
|
||||
cacheTimeout,
|
||||
startTime: Date.now(),
|
||||
@ -160,7 +158,7 @@ export class PanelQueryRunner {
|
||||
});
|
||||
|
||||
const lowerIntervalLimit = minInterval ? templateSrv.replace(minInterval, request.scopedVars) : ds.interval;
|
||||
const norm = kbn.calculateInterval(timeRange, widthPixels, lowerIntervalLimit);
|
||||
const norm = kbn.calculateInterval(timeRange, maxDataPoints, lowerIntervalLimit);
|
||||
|
||||
// make shallow copy of scoped vars,
|
||||
// and add built in variables interval and interval_ms
|
||||
|
@ -188,8 +188,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
timezone: this.dashboard.getTimezone(),
|
||||
timeInfo: this.timeInfo,
|
||||
timeRange: this.range,
|
||||
widthPixels: this.width,
|
||||
maxDataPoints: panel.maxDataPoints,
|
||||
maxDataPoints: panel.maxDataPoints || this.width,
|
||||
minInterval: panel.interval,
|
||||
scopedVars: panel.scopedVars,
|
||||
cacheTimeout: panel.cacheTimeout,
|
||||
|
@ -25,12 +25,12 @@ export const PromSettings = (props: Props) => {
|
||||
<FormField
|
||||
label="Scrape interval"
|
||||
labelWidth={13}
|
||||
placeholder="15s"
|
||||
inputEl={
|
||||
<Input
|
||||
className="width-6"
|
||||
value={value.jsonData.timeInterval}
|
||||
spellCheck={false}
|
||||
placeholder="15s"
|
||||
onChange={onChangeHandler('timeInterval', value, onChange)}
|
||||
validationEvents={promSettingsValidationEvents}
|
||||
/>
|
||||
|
@ -90,6 +90,10 @@ $input-border: 1px solid $input-border-color;
|
||||
.select-container {
|
||||
margin-right: $space-xs;
|
||||
}
|
||||
|
||||
.gf-form-spacing {
|
||||
margin-right: $space-xs;
|
||||
}
|
||||
}
|
||||
|
||||
.gf-form-button-row {
|
||||
|
Loading…
Reference in New Issue
Block a user