Alerting: Add min interval option to alert rule query creation (#71986)

This features adds a configuration option when creating an alert rule query. This option already exists as part of the alert query model but is not currently configurable through the UI.
This commit is contained in:
Matthew Jacobson 2023-07-24 11:20:55 -04:00 committed by GitHub
parent 5c1e8c108a
commit 8ffd2a71d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 6 deletions

View File

@ -6,7 +6,7 @@ import { relativeToTimeRange } from '@grafana/data/src/datetime/rangeutil';
import { clearButtonStyles, Icon, RelativeTimeRangePicker, Toggletip, useStyles2 } from '@grafana/ui';
import { AlertQuery } from 'app/types/unified-alerting-dto';
import { AlertQueryOptions, MaxDataPointsOption } from './QueryWrapper';
import { AlertQueryOptions, MaxDataPointsOption, MinIntervalOption } from './QueryWrapper';
export interface QueryOptionsProps {
query: AlertQuery;
@ -50,6 +50,7 @@ export const QueryOptions = ({
options={queryOptions}
onChange={(options) => onChangeQueryOptions(options, index)}
/>
<MinIntervalOption options={queryOptions} onChange={(options) => onChangeQueryOptions(options, index)} />
</div>
</div>
}
@ -67,7 +68,8 @@ export const QueryOptions = ({
.locale('en')
.fromNow(true)}
</span>
{queryOptions.maxDataPoints && <span>, MD {queryOptions.maxDataPoints}</span>}
{queryOptions.maxDataPoints && <span>, MD = {queryOptions.maxDataPoints}</span>}
{queryOptions.minInterval && <span>, Min. Interval = {queryOptions.minInterval}</span>}
</div>
</>
);

View File

@ -2,7 +2,14 @@ import { omit } from 'lodash';
import React, { PureComponent, useState } from 'react';
import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
import { DataQuery, DataSourceInstanceSettings, LoadingState, PanelData, RelativeTimeRange } from '@grafana/data';
import {
DataQuery,
DataSourceInstanceSettings,
LoadingState,
PanelData,
rangeUtil,
RelativeTimeRange,
} from '@grafana/data';
import { Stack } from '@grafana/experimental';
import { getDataSourceSrv } from '@grafana/runtime';
import { Button, Card, Icon } from '@grafana/ui';
@ -61,7 +68,11 @@ export class QueryRows extends PureComponent<Props> {
}
return {
...item,
model: { ...item.model, maxDataPoints: options.maxDataPoints },
model: {
...item.model,
maxDataPoints: options.maxDataPoints,
intervalMs: options.minInterval ? rangeUtil.intervalToMs(options.minInterval) : undefined,
},
};
})
);

View File

@ -18,15 +18,18 @@ import { GraphTresholdsStyleMode, Icon, InlineFormLabel, Input, Tooltip, useStyl
import { QueryEditorRow } from 'app/features/query/components/QueryEditorRow';
import { AlertQuery } from 'app/types/unified-alerting-dto';
import { msToSingleUnitDuration } from '../../utils/time';
import { AlertConditionIndicator } from '../expressions/AlertConditionIndicator';
import { QueryOptions } from './QueryOptions';
import { VizWrapper } from './VizWrapper';
export const DEFAULT_MAX_DATA_POINTS = 43200;
export const DEFAULT_MIN_INTERVAL = '1s';
export interface AlertQueryOptions {
maxDataPoints?: number | undefined;
minInterval?: string | undefined;
}
interface Props {
@ -107,9 +110,13 @@ export const QueryWrapper = ({
// TODO add a warning label here too when the data looks like time series data and is used as an alert condition
function HeaderExtras({ query, error, index }: { query: AlertQuery; error?: Error; index: number }) {
const queryOptions: AlertQueryOptions = { maxDataPoints: query.model.maxDataPoints };
const queryOptions: AlertQueryOptions = {
maxDataPoints: query.model.maxDataPoints,
minInterval: query.model.intervalMs ? msToSingleUnitDuration(query.model.intervalMs) : undefined,
};
const alertQueryOptions: AlertQueryOptions = {
maxDataPoints: queryOptions.maxDataPoints,
minInterval: queryOptions.minInterval,
};
return (
@ -222,6 +229,50 @@ export function MaxDataPointsOption({
);
}
export function MinIntervalOption({
options,
onChange,
}: {
options: AlertQueryOptions;
onChange: (options: AlertQueryOptions) => void;
}) {
const value = options.minInterval ?? '';
const onMinIntervalBlur = (event: ChangeEvent<HTMLInputElement>) => {
const minInterval = event.target.value;
if (minInterval !== value) {
onChange({
...options,
minInterval,
});
}
};
return (
<Stack direction="row" alignItems="baseline" gap={1}>
<InlineFormLabel
width={8}
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.
</>
}
>
Min interval
</InlineFormLabel>
<Input
type="text"
className="width-6"
placeholder={DEFAULT_MIN_INTERVAL}
spellCheck={false}
onBlur={onMinIntervalBlur}
defaultValue={value}
/>
</Stack>
);
}
const getStyles = (theme: GrafanaTheme2) => ({
wrapper: css`
label: AlertingQueryWrapper;

View File

@ -1,6 +1,6 @@
import { createAction, createReducer } from '@reduxjs/toolkit';
import { DataQuery, getDefaultRelativeTimeRange, RelativeTimeRange } from '@grafana/data';
import { DataQuery, getDefaultRelativeTimeRange, rangeUtil, RelativeTimeRange } from '@grafana/data';
import { getNextRefIdChar } from 'app/core/utils/query';
import { findDataSourceFromExpressionRecursive } from 'app/features/alerting/utils/dataSourceFromExpression';
import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
@ -43,6 +43,7 @@ export const rewireExpressions = createAction<{ oldRefId: string; newRefId: stri
export const updateExpressionType = createAction<{ refId: string; type: ExpressionQueryType }>('updateExpressionType');
export const updateExpressionTimeRange = createAction('updateExpressionTimeRange');
export const updateMaxDataPoints = createAction<{ refId: string; maxDataPoints: number }>('updateMaxDataPoints');
export const updateMinInterval = createAction<{ refId: string; minInterval: string }>('updateMinInterval');
export const setRecordingRulesQueries = createAction<{ recordingRuleQueries: AlertQuery[]; expression: string }>(
'setRecordingRulesQueries'
@ -96,6 +97,19 @@ export const queriesAndExpressionsReducer = createReducer(initialState, (builder
}
: query;
});
})
.addCase(updateMinInterval, (state, action) => {
state.queries = state.queries.map((query) => {
return query.refId === action.payload.refId
? {
...query,
model: {
...query.model,
intervalMs: action.payload.minInterval ? rangeUtil.intervalToMs(action.payload.minInterval) : undefined,
},
}
: query;
});
});
// expressions actions

View File

@ -106,3 +106,17 @@ export const safeParseDurationstr = (duration: string): number => {
export const isNullDate = (date: string) => {
return date.includes('0001-01-01T00');
};
// Format given time span in MS to the largest single unit duration string up to hours.
export function msToSingleUnitDuration(rangeMs: number): string {
if (rangeMs % (1000 * 60 * 60) === 0) {
return rangeMs / (1000 * 60 * 60) + 'h';
}
if (rangeMs % (1000 * 60) === 0) {
return rangeMs / (1000 * 60) + 'm';
}
if (rangeMs % 1000 === 0) {
return rangeMs / 1000 + 's';
}
return rangeMs.toFixed() + 'ms';
}