mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
5c1e8c108a
commit
8ffd2a71d3
@ -6,7 +6,7 @@ import { relativeToTimeRange } from '@grafana/data/src/datetime/rangeutil';
|
|||||||
import { clearButtonStyles, Icon, RelativeTimeRangePicker, Toggletip, useStyles2 } from '@grafana/ui';
|
import { clearButtonStyles, Icon, RelativeTimeRangePicker, Toggletip, useStyles2 } from '@grafana/ui';
|
||||||
import { AlertQuery } from 'app/types/unified-alerting-dto';
|
import { AlertQuery } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
import { AlertQueryOptions, MaxDataPointsOption } from './QueryWrapper';
|
import { AlertQueryOptions, MaxDataPointsOption, MinIntervalOption } from './QueryWrapper';
|
||||||
|
|
||||||
export interface QueryOptionsProps {
|
export interface QueryOptionsProps {
|
||||||
query: AlertQuery;
|
query: AlertQuery;
|
||||||
@ -50,6 +50,7 @@ export const QueryOptions = ({
|
|||||||
options={queryOptions}
|
options={queryOptions}
|
||||||
onChange={(options) => onChangeQueryOptions(options, index)}
|
onChange={(options) => onChangeQueryOptions(options, index)}
|
||||||
/>
|
/>
|
||||||
|
<MinIntervalOption options={queryOptions} onChange={(options) => onChangeQueryOptions(options, index)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -67,7 +68,8 @@ export const QueryOptions = ({
|
|||||||
.locale('en')
|
.locale('en')
|
||||||
.fromNow(true)}
|
.fromNow(true)}
|
||||||
</span>
|
</span>
|
||||||
{queryOptions.maxDataPoints && <span>, MD {queryOptions.maxDataPoints}</span>}
|
{queryOptions.maxDataPoints && <span>, MD = {queryOptions.maxDataPoints}</span>}
|
||||||
|
{queryOptions.minInterval && <span>, Min. Interval = {queryOptions.minInterval}</span>}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,14 @@ import { omit } from 'lodash';
|
|||||||
import React, { PureComponent, useState } from 'react';
|
import React, { PureComponent, useState } from 'react';
|
||||||
import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
|
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 { Stack } from '@grafana/experimental';
|
||||||
import { getDataSourceSrv } from '@grafana/runtime';
|
import { getDataSourceSrv } from '@grafana/runtime';
|
||||||
import { Button, Card, Icon } from '@grafana/ui';
|
import { Button, Card, Icon } from '@grafana/ui';
|
||||||
@ -61,7 +68,11 @@ export class QueryRows extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...item,
|
...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 { QueryEditorRow } from 'app/features/query/components/QueryEditorRow';
|
||||||
import { AlertQuery } from 'app/types/unified-alerting-dto';
|
import { AlertQuery } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
|
import { msToSingleUnitDuration } from '../../utils/time';
|
||||||
import { AlertConditionIndicator } from '../expressions/AlertConditionIndicator';
|
import { AlertConditionIndicator } from '../expressions/AlertConditionIndicator';
|
||||||
|
|
||||||
import { QueryOptions } from './QueryOptions';
|
import { QueryOptions } from './QueryOptions';
|
||||||
import { VizWrapper } from './VizWrapper';
|
import { VizWrapper } from './VizWrapper';
|
||||||
|
|
||||||
export const DEFAULT_MAX_DATA_POINTS = 43200;
|
export const DEFAULT_MAX_DATA_POINTS = 43200;
|
||||||
|
export const DEFAULT_MIN_INTERVAL = '1s';
|
||||||
|
|
||||||
export interface AlertQueryOptions {
|
export interface AlertQueryOptions {
|
||||||
maxDataPoints?: number | undefined;
|
maxDataPoints?: number | undefined;
|
||||||
|
minInterval?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
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
|
// 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 }) {
|
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 = {
|
const alertQueryOptions: AlertQueryOptions = {
|
||||||
maxDataPoints: queryOptions.maxDataPoints,
|
maxDataPoints: queryOptions.maxDataPoints,
|
||||||
|
minInterval: queryOptions.minInterval,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
wrapper: css`
|
wrapper: css`
|
||||||
label: AlertingQueryWrapper;
|
label: AlertingQueryWrapper;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createAction, createReducer } from '@reduxjs/toolkit';
|
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 { getNextRefIdChar } from 'app/core/utils/query';
|
||||||
import { findDataSourceFromExpressionRecursive } from 'app/features/alerting/utils/dataSourceFromExpression';
|
import { findDataSourceFromExpressionRecursive } from 'app/features/alerting/utils/dataSourceFromExpression';
|
||||||
import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
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 updateExpressionType = createAction<{ refId: string; type: ExpressionQueryType }>('updateExpressionType');
|
||||||
export const updateExpressionTimeRange = createAction('updateExpressionTimeRange');
|
export const updateExpressionTimeRange = createAction('updateExpressionTimeRange');
|
||||||
export const updateMaxDataPoints = createAction<{ refId: string; maxDataPoints: number }>('updateMaxDataPoints');
|
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 }>(
|
export const setRecordingRulesQueries = createAction<{ recordingRuleQueries: AlertQuery[]; expression: string }>(
|
||||||
'setRecordingRulesQueries'
|
'setRecordingRulesQueries'
|
||||||
@ -96,6 +97,19 @@ export const queriesAndExpressionsReducer = createReducer(initialState, (builder
|
|||||||
}
|
}
|
||||||
: query;
|
: 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
|
// expressions actions
|
||||||
|
@ -106,3 +106,17 @@ export const safeParseDurationstr = (duration: string): number => {
|
|||||||
export const isNullDate = (date: string) => {
|
export const isNullDate = (date: string) => {
|
||||||
return date.includes('0001-01-01T00');
|
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…
Reference in New Issue
Block a user