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.
pull/72204/head^2
Matthew Jacobson 2 years ago committed by GitHub
parent 5c1e8c108a
commit 8ffd2a71d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      public/app/features/alerting/unified/components/rule-editor/QueryOptions.tsx
  2. 15
      public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx
  3. 53
      public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx
  4. 16
      public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts
  5. 14
      public/app/features/alerting/unified/utils/time.ts

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

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

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

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

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

Loading…
Cancel
Save