Alerting: Remove threshold as default expression for grafana-managed recording rules (#94949)
* remove threshold as default expression for grafana-managed recording rule * fix * remove eslint disable line
This commit is contained in:
@@ -20,6 +20,8 @@ import {
|
|||||||
import { AlertQuery, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
import { AlertQuery, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
import { usePagination } from '../../hooks/usePagination';
|
import { usePagination } from '../../hooks/usePagination';
|
||||||
|
import { RuleFormValues } from '../../types/rule-form';
|
||||||
|
import { isGrafanaRecordingRuleByType } from '../../utils/rules';
|
||||||
import { PopupCard } from '../HoverCard';
|
import { PopupCard } from '../HoverCard';
|
||||||
import { Spacer } from '../Spacer';
|
import { Spacer } from '../Spacer';
|
||||||
import { AlertStateTag } from '../rules/AlertStateTag';
|
import { AlertStateTag } from '../rules/AlertStateTag';
|
||||||
@@ -58,7 +60,9 @@ export const Expression: FC<ExpressionProps> = ({
|
|||||||
|
|
||||||
const queryType = query?.type;
|
const queryType = query?.type;
|
||||||
|
|
||||||
const { setError, clearErrors } = useFormContext();
|
const { setError, clearErrors, watch } = useFormContext<RuleFormValues>();
|
||||||
|
const type = watch('type');
|
||||||
|
const isGrafanaRecordingRule = type ? isGrafanaRecordingRuleByType(type) : false;
|
||||||
|
|
||||||
const onQueriesValidationError = useCallback(
|
const onQueriesValidationError = useCallback(
|
||||||
(errorMsg: string | undefined) => {
|
(errorMsg: string | undefined) => {
|
||||||
@@ -158,20 +162,25 @@ export const Expression: FC<ExpressionProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
{hasResults && (
|
{hasResults && (
|
||||||
<>
|
<>
|
||||||
<ExpressionResult series={series} isAlertCondition={isAlertCondition} />
|
<ExpressionResult
|
||||||
|
series={series}
|
||||||
|
isAlertCondition={isAlertCondition}
|
||||||
|
isRecordingRule={isGrafanaRecordingRule}
|
||||||
|
/>
|
||||||
|
{!isGrafanaRecordingRule && (
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<Stack direction="row" alignItems="center">
|
||||||
|
<Spacer />
|
||||||
|
|
||||||
<div className={styles.footer}>
|
<PreviewSummary
|
||||||
<Stack direction="row" alignItems="center">
|
isCondition={Boolean(isAlertCondition)}
|
||||||
<Spacer />
|
firing={groupedByState[PromAlertingRuleState.Firing].length}
|
||||||
|
normal={groupedByState[PromAlertingRuleState.Inactive].length}
|
||||||
<PreviewSummary
|
seriesCount={seriesCount}
|
||||||
isCondition={Boolean(isAlertCondition)}
|
/>
|
||||||
firing={groupedByState[PromAlertingRuleState.Firing].length}
|
</Stack>
|
||||||
normal={groupedByState[PromAlertingRuleState.Inactive].length}
|
</div>
|
||||||
seriesCount={seriesCount}
|
)}
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -182,9 +191,10 @@ export const Expression: FC<ExpressionProps> = ({
|
|||||||
interface ExpressionResultProps {
|
interface ExpressionResultProps {
|
||||||
series: DataFrame[];
|
series: DataFrame[];
|
||||||
isAlertCondition?: boolean;
|
isAlertCondition?: boolean;
|
||||||
|
isRecordingRule?: boolean;
|
||||||
}
|
}
|
||||||
export const PAGE_SIZE = 20;
|
export const PAGE_SIZE = 20;
|
||||||
export const ExpressionResult: FC<ExpressionResultProps> = ({ series, isAlertCondition }) => {
|
export const ExpressionResult: FC<ExpressionResultProps> = ({ series, isAlertCondition, isRecordingRule = false }) => {
|
||||||
const { pageItems, previousPage, nextPage, numberOfPages, pageStart, pageEnd } = usePagination(series, 1, PAGE_SIZE);
|
const { pageItems, previousPage, nextPage, numberOfPages, pageStart, pageEnd } = usePagination(series, 1, PAGE_SIZE);
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
@@ -212,7 +222,13 @@ export const ExpressionResult: FC<ExpressionResultProps> = ({ series, isAlertCon
|
|||||||
!isTimeSeriesResults &&
|
!isTimeSeriesResults &&
|
||||||
pageItems.map((frame, index) => (
|
pageItems.map((frame, index) => (
|
||||||
// There's no way to uniquely identify a frame that doesn't cause render bugs :/ (Gilles)
|
// There's no way to uniquely identify a frame that doesn't cause render bugs :/ (Gilles)
|
||||||
<FrameRow key={uniqueId()} frame={frame} index={pageStart + index} isAlertCondition={isAlertCondition} />
|
<FrameRow
|
||||||
|
key={uniqueId()}
|
||||||
|
frame={frame}
|
||||||
|
index={pageStart + index}
|
||||||
|
isAlertCondition={isAlertCondition}
|
||||||
|
isRecordingRule={isRecordingRule}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
{emptyResults && <div className={cx(styles.expression.noData, styles.mutedText)}>No data</div>}
|
{emptyResults && <div className={cx(styles.expression.noData, styles.mutedText)}>No data</div>}
|
||||||
{shouldShowPagination && (
|
{shouldShowPagination && (
|
||||||
@@ -353,6 +369,7 @@ const Header: FC<HeaderProps> = ({
|
|||||||
interface FrameProps extends Pick<ExpressionProps, 'isAlertCondition'> {
|
interface FrameProps extends Pick<ExpressionProps, 'isAlertCondition'> {
|
||||||
frame: DataFrame;
|
frame: DataFrame;
|
||||||
index: number;
|
index: number;
|
||||||
|
isRecordingRule?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OpeningBracket = () => <span>{'{'}</span>;
|
const OpeningBracket = () => <span>{'{'}</span>;
|
||||||
@@ -361,7 +378,7 @@ const ClosingBracket = () => <span>{'}'}</span>;
|
|||||||
const Quote = () => <span>"</span>;
|
const Quote = () => <span>"</span>;
|
||||||
const Equals = () => <span>{'='}</span>;
|
const Equals = () => <span>{'='}</span>;
|
||||||
|
|
||||||
const FrameRow: FC<FrameProps> = ({ frame, index, isAlertCondition }) => {
|
function FrameRow({ frame, index, isAlertCondition, isRecordingRule }: FrameProps) {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const name = getSeriesName(frame) || 'Series ' + index;
|
const name = getSeriesName(frame) || 'Series ' + index;
|
||||||
@@ -374,6 +391,7 @@ const FrameRow: FC<FrameProps> = ({ frame, index, isAlertCondition }) => {
|
|||||||
const showNormal = isAlertCondition && value === 0;
|
const showNormal = isAlertCondition && value === 0;
|
||||||
|
|
||||||
const title = `${hasLabels ? '' : name}${hasLabels ? `{${formatLabels(labelsRecord)}}` : ''}`;
|
const title = `${hasLabels ? '' : name}${hasLabels ? `{${formatLabels(labelsRecord)}}` : ''}`;
|
||||||
|
const shouldRenderSumary = !isRecordingRule;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.expression.resultsRow}>
|
<div className={styles.expression.resultsRow}>
|
||||||
@@ -401,14 +419,18 @@ const FrameRow: FC<FrameProps> = ({ frame, index, isAlertCondition }) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.expression.resultValue}>{value}</div>
|
<div className={styles.expression.resultValue}>{value}</div>
|
||||||
{showFiring && <AlertStateTag state={PromAlertingRuleState.Firing} size="sm" />}
|
{shouldRenderSumary && (
|
||||||
{showNormal && <AlertStateTag state={PromAlertingRuleState.Inactive} size="sm" />}
|
<>
|
||||||
|
{showFiring && <AlertStateTag state={PromAlertingRuleState.Firing} size="sm" />}
|
||||||
|
{showNormal && <AlertStateTag state={PromAlertingRuleState.Inactive} size="sm" />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
interface TimeseriesRowProps extends Omit<FrameProps, 'isRecordingRule'> {}
|
||||||
const TimeseriesRow: FC<FrameProps & { index: number }> = ({ frame, index }) => {
|
const TimeseriesRow: FC<TimeseriesRowProps & { index: number }> = ({ frame, index }) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const valueField = frame.fields[1]; // field 0 is "time", field 1 is "value"
|
const valueField = frame.fields[1]; // field 0 is "time", field 1 is "value"
|
||||||
|
|||||||
+4
-2
@@ -53,6 +53,7 @@ import {
|
|||||||
} from '../../../utils/rule-form';
|
} from '../../../utils/rule-form';
|
||||||
import * as ruleId from '../../../utils/rule-id';
|
import * as ruleId from '../../../utils/rule-id';
|
||||||
import { fromRulerRule, fromRulerRuleAndRuleGroupIdentifier, stringifyIdentifier } from '../../../utils/rule-id';
|
import { fromRulerRule, fromRulerRuleAndRuleGroupIdentifier, stringifyIdentifier } from '../../../utils/rule-id';
|
||||||
|
import { isGrafanaRecordingRuleByType } from '../../../utils/rules';
|
||||||
import { createRelativeUrl } from '../../../utils/url';
|
import { createRelativeUrl } from '../../../utils/url';
|
||||||
import { GrafanaRuleExporter } from '../../export/GrafanaRuleExporter';
|
import { GrafanaRuleExporter } from '../../export/GrafanaRuleExporter';
|
||||||
import { AlertRuleNameAndMetric } from '../AlertRuleNameInput';
|
import { AlertRuleNameAndMetric } from '../AlertRuleNameInput';
|
||||||
@@ -102,12 +103,13 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
|
|||||||
if (queryParams.has('defaults')) {
|
if (queryParams.has('defaults')) {
|
||||||
return formValuesFromQueryParams(queryParams.get('defaults') ?? '', ruleType);
|
return formValuesFromQueryParams(queryParams.get('defaults') ?? '', ruleType);
|
||||||
}
|
}
|
||||||
|
const defaultRuleType = ruleType || RuleFormType.grafana;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...getDefaultFormValues(),
|
...getDefaultFormValues(),
|
||||||
condition: 'C',
|
condition: 'C',
|
||||||
queries: getDefaultQueries(),
|
queries: getDefaultQueries(isGrafanaRecordingRuleByType(defaultRuleType)),
|
||||||
type: ruleType || RuleFormType.grafana,
|
type: defaultRuleType,
|
||||||
evaluateEvery: evaluateEvery,
|
evaluateEvery: evaluateEvery,
|
||||||
};
|
};
|
||||||
}, [existing, prefill, queryParams, evaluateEvery, ruleType]);
|
}, [existing, prefill, queryParams, evaluateEvery, ruleType]);
|
||||||
|
|||||||
@@ -497,14 +497,16 @@ export function recordingRulerRuleToRuleForm(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDefaultQueries = (): AlertQuery[] => {
|
export const getDefaultQueries = (isRecordingRule = false): AlertQuery[] => {
|
||||||
const dataSource = getDefaultOrFirstCompatibleDataSource();
|
const dataSource = getDefaultOrFirstCompatibleDataSource();
|
||||||
|
|
||||||
if (!dataSource) {
|
if (!dataSource) {
|
||||||
return [...getDefaultExpressions('A', 'B')];
|
const expressions = isRecordingRule ? getDefaultExpressionsForRecording('A') : getDefaultExpressions('A', 'B');
|
||||||
|
return [...expressions];
|
||||||
}
|
}
|
||||||
const relativeTimeRange = getDefaultRelativeTimeRange();
|
const relativeTimeRange = getDefaultRelativeTimeRange();
|
||||||
|
|
||||||
|
const expressions = isRecordingRule ? getDefaultExpressionsForRecording('B') : getDefaultExpressions('B', 'C');
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
refId: 'A',
|
refId: 'A',
|
||||||
@@ -515,7 +517,7 @@ export const getDefaultQueries = (): AlertQuery[] => {
|
|||||||
refId: 'A',
|
refId: 'A',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...getDefaultExpressions('B', 'C'),
|
...expressions,
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -536,7 +538,6 @@ export const getDefaultRecordingRulesQueries = (
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDefaultExpressions = (...refIds: [string, string]): AlertQuery[] => {
|
const getDefaultExpressions = (...refIds: [string, string]): AlertQuery[] => {
|
||||||
const refOne = refIds[0];
|
const refOne = refIds[0];
|
||||||
const refTwo = refIds[1];
|
const refTwo = refIds[1];
|
||||||
@@ -615,6 +616,46 @@ const getDefaultExpressions = (...refIds: [string, string]): AlertQuery[] => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
const getDefaultExpressionsForRecording = (refOne: string): AlertQuery[] => {
|
||||||
|
const reduceExpression: ExpressionQuery = {
|
||||||
|
refId: refOne,
|
||||||
|
type: ExpressionQueryType.reduce,
|
||||||
|
datasource: {
|
||||||
|
uid: ExpressionDatasourceUID,
|
||||||
|
type: ExpressionDatasourceRef.type,
|
||||||
|
},
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
type: 'query',
|
||||||
|
evaluator: {
|
||||||
|
params: [],
|
||||||
|
type: EvalFunction.IsAbove,
|
||||||
|
},
|
||||||
|
operator: {
|
||||||
|
type: 'and',
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
params: [refOne],
|
||||||
|
},
|
||||||
|
reducer: {
|
||||||
|
params: [],
|
||||||
|
type: 'last',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
reducer: 'last',
|
||||||
|
expression: 'A',
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
refId: refOne,
|
||||||
|
datasourceUid: ExpressionDatasourceUID,
|
||||||
|
queryType: '',
|
||||||
|
model: reduceExpression,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
const dataQueriesToGrafanaQueries = async (
|
const dataQueriesToGrafanaQueries = async (
|
||||||
queries: DataQuery[],
|
queries: DataQuery[],
|
||||||
|
|||||||
Reference in New Issue
Block a user