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:
Torkel Ödegaard 2020-04-27 18:29:41 +02:00 committed by GitHub
parent 32492dd650
commit 0742dbc9be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 161 additions and 127 deletions

View File

@ -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};
`,
};
});

View File

@ -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,

View File

@ -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>
);
};

View File

@ -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;

View File

@ -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>

View File

@ -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(

View File

@ -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

View File

@ -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,

View File

@ -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}
/>

View File

@ -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 {