chore: add `Buckets Diff` to alerting rule (#118)

* chore: adjust conditions of alerting rule

* chore: add stat periods to buckets diff

* chore: adjust minimum_period_match

* fix: lose config while editing the alerting rule

* chore: adjust buckets diff in Alerting Rule

* chore: adjust bucket diff styles

* chore: adjust type `Content` in `Buckets Diff`

* chore: add `Condition Type` to alerting message detail

* chore: update release notes

---------

Co-authored-by: yaojiping <yaojiping@infini.ltd>
Co-authored-by: Hardy <luohoufu@infinilabs.com>
This commit is contained in:
yaojp123 2025-02-12 15:57:07 +08:00 committed by GitHub
parent 3adfc77466
commit 80e2a4356a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 427 additions and 112 deletions

View File

@ -17,6 +17,8 @@ Information about release notes of INFINI Console is provided here.
### Improvements
- add Buckets Diff to alerting rule
## 1.28.1 (2025-01-24)
- add credential settings for agent in enrolling agent

View File

@ -17,6 +17,7 @@ title: "版本历史"
### Improvements
- 告警规则新增分桶对比
- 在注册 Agent 中新增 Agent 凭据设置
- 在集群编辑中新增采集模式

View File

@ -223,6 +223,7 @@ export default {
//Configure alert objects 配置告警对象
"alert.rule.form.title.configure_alert_object": "Configure alert objects",
"alert.rule.form.label.alert_metric": "Metrics",
"alert.rule.form.label.bucket_label_template": "Bucket Label Template",
"alert.rule.form.label.alert_metric.groups": "Groups",
"alert.rule.form.label.alert_metric.button.add_group": "Add group",
"alert.rule.form.label.alert_metric.button.add_metric": "Add metrics",
@ -237,7 +238,13 @@ export default {
"alert.rule.form.label.alert_condition": "Conditions",
"alert.rule.form.label.event_title": "Event title",
"alert.rule.form.label.event_message": "Event message",
"alert.rule.form.label.metrics_value": "Metrics value",
"alert.rule.form.label.buckets_diff": "Buckets diff",
"alert.rule.form.label.above_metric": "Above metrics",
"alert.rule.form.label.size": "Docs count",
"alert.rule.form.label.content": "Content",
"alert.rule.form.label.in": "In",
"alert.rule.form.label.content.changed": "Changed",
"alert.rule.form.label.lasts_periods": "Lasts {num} periods",
"alert.rule.form.button.add_condition": "Add condition",
"alert.rule.form.label.trigger": "Trigger",
@ -326,7 +333,9 @@ export default {
"alert.message.detail.action_result": "Execution result",
"alert.message.detail.action_result_error": "Exection error",
"alert.message.detail.alert_info": "Alert Detail",
"alert.message.detail.condition.type": "Condition Type",
"alert.message.detail.condition": "Condition",
"alert.message.detail.bucket_diff_type": "Bucket Diff Type",
"alert.message.detail.recover_time": "Recovered Time",
"alert.message.detail.title.event_detail": "Event Detail",
"alert.message.detail.title.summary": "Summary",

View File

@ -211,6 +211,7 @@ export default {
//Configure alert objects 配置告警对象
"alert.rule.form.title.configure_alert_object": "配置告警对象",
"alert.rule.form.label.alert_metric": "告警指标",
"alert.rule.form.label.bucket_label_template": "分桶标签模板",
"alert.rule.form.label.alert_metric.groups": "指标分组",
"alert.rule.form.label.alert_metric.button.add_group": "添加分组",
"alert.rule.form.label.alert_metric.button.add_metric": "添加指标",
@ -225,7 +226,13 @@ export default {
"alert.rule.form.label.alert_condition": "告警条件",
"alert.rule.form.label.event_title": "事件标题",
"alert.rule.form.label.event_message": "事件内容",
"alert.rule.form.label.metrics_value": "指标数值",
"alert.rule.form.label.buckets_diff": "分桶对比",
"alert.rule.form.label.above_metric": "以上指标",
"alert.rule.form.label.size": "文档数",
"alert.rule.form.label.content": "内容",
"alert.rule.form.label.in": "在",
"alert.rule.form.label.content.changed": "变更",
"alert.rule.form.label.lasts_periods": "持续{num}个周期",
"alert.rule.form.button.add_condition": "添加条件",
"alert.rule.form.label.trigger": "触发",
@ -309,7 +316,9 @@ export default {
"alert.message.detail.action_result": "执行结果",
"alert.message.detail.action_result_error": "规则执行错误",
"alert.message.detail.alert_info": "告警详情",
"alert.message.detail.condition.type": "触发条件类型",
"alert.message.detail.condition": "触发条件",
"alert.message.detail.bucket_diff_type": "分桶对比类型",
"alert.message.detail.recover_time": "恢复时间",
"alert.message.detail.title.event_detail": "事件详情",
"alert.message.detail.title.summary": "概览",

View File

@ -8,6 +8,9 @@ import EventMessageStatus from "./EventMessageStatus";
export default ({msgItem})=>{
const labelSpan = 6;
const vSpan = 18;
const isBucketDiff = !!(msgItem && msgItem.bucket_conditions)
return (
<Card size={"small"} title={formatMessage({ id: "alert.message.detail.title.event_detail" })}>
<div style={{lineHeight:"2em"}} >
@ -46,6 +49,18 @@ export default ({msgItem})=>{
<Col span={labelSpan}>{formatMessage({ id: "alert.message.table.duration" })}</Col>
<Col span={vSpan}>{moment.duration(msgItem?.duration).humanize()}</Col>
</Row>
<Row>
<Col span={labelSpan}>{formatMessage({ id: "alert.message.detail.condition.type" })}</Col>
<Col span={vSpan}>{isBucketDiff ? formatMessage({id: `alert.rule.form.label.buckets_diff`}) : formatMessage({id: `alert.rule.form.label.metrics_value`})}</Col>
</Row>
{
isBucketDiff && msgItem?.bucket_diff_type ? (
<Row>
<Col span={labelSpan}>{formatMessage({ id: "alert.message.detail.bucket_diff_type" })}</Col>
<Col span={vSpan}>{formatMessage({id: `alert.rule.form.label.${msgItem.bucket_diff_type}`}) }</Col>
</Row>
) : null
}
<Row>
<Col span={labelSpan}>{formatMessage({ id: "alert.message.detail.condition" })}</Col>
<Col span={vSpan}>{msgItem?.hit_condition}</Col>

View File

@ -69,18 +69,20 @@ export default Form.create({ name: "rule_form_edit" })((props) => {
const [editValue] = useMemo(() => {
let editValue = value?._source || {};
if (editValue?.metrics && editValue?.conditions) {
if (editValue?.metrics && (editValue?.conditions || editValue?.bucket_conditions)) {
editValue.alert_objects = [
{
name: editValue.name,
metrics: editValue.metrics,
conditions: editValue.conditions,
bucket_conditions: editValue.bucket_conditions,
schedule: editValue.schedule,
},
];
delete editValue.name;
delete editValue.metrics;
delete editValue.conditions;
delete editValue.bucket_conditions;
delete editValue.schedule;
}
return [editValue];

View File

@ -231,6 +231,7 @@ const RuleForm = (props) => {
delete values.alert_objects;
alert_objects = alert_objects.map((alert_object) => {
if (alert_object.conditions) {
alert_object.conditions["operator"] = "any";
alert_object.conditions.items = alert_object.conditions.items.map(
(item) => {
@ -240,7 +241,10 @@ const RuleForm = (props) => {
};
}
);
}
if (alert_object.bucket_conditions?.items) {
alert_object.bucket_conditions.items = alert_object.bucket_conditions.items.filter((item) => !!item.type);
}
return { ...values, ...alert_object };
});
return alert_objects;

View File

@ -1,13 +1,45 @@
import { Form, Input, Select, Button, Icon } from "antd";
import { useCallback, useMemo, useState } from "react";
import { Form, Input, Select, Button, Icon, Radio, InputNumber } from "antd";
import { useCallback, useEffect, useMemo, useState } from "react";
import "./form.scss";
import { formatMessage } from "umi/locale";
import { PriorityColor } from "../utils/constants";
import { cloneDeep } from "lodash";
const { Option } = Select;
const InputGroup = Input.Group;
const lastsPeriods = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15]
const operators = ['equals', 'gte', 'gt', 'lt', 'lte', 'range']
const FormAlertCondition = (props) => {
const { conditions, bucketConditions } = props;
const [type, setType] = useState('metrics_value')
useEffect(() => {
if (bucketConditions?.items?.length > 0) {
setType('buckets_diff')
}
}, [JSON.stringify(conditions), JSON.stringify(bucketConditions)])
return (
<>
<Radio.Group value={type} onChange={(e) => setType(e.target.value)}>
{
['metrics_value', 'buckets_diff'].map((item) => (
<Radio.Button key={item} value={item}>{formatMessage({
id: `alert.rule.form.label.${item}`,
})}</Radio.Button>
))
}
</Radio.Group>
{ type === 'metrics_value' ? <MetricsValue {...props} /> : <BucketsDiff {...props} /> }
</>
)
};
export default FormAlertCondition;
const MetricsValue = (props) => {
const { getFieldDecorator } = props.form;
const alertObjectIndex = props.alertObjectIndex || 0;
const conditions = props.conditions || {};
@ -72,95 +104,18 @@ const FormAlertCondition = (props) => {
props.onPreviewChartChange();
}}
>
<Option value="1">
{
lastsPeriods.map((item) => (
<Option key={`${item}`} value={`${item}`}>
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 1 }
)}
</Option>
<Option value="2">
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 2 }
)}
</Option>
<Option value="3">
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 3 }
)}
</Option>
<Option value="4">
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 4 }
)}
</Option>
<Option value="5">
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 5 }
)}
</Option>
<Option value="6">
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 6 }
)}
</Option>
<Option value="7">
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 7 }
)}
</Option>
<Option value="8">
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 8 }
)}
</Option>
<Option value="9">
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 9 }
)}
</Option>
<Option value="10">
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 10 }
)}
</Option>
<Option value="15">
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 15 }
{ num: item }
)}
</Option>
))
}
</Select>
)}
</Form.Item>
@ -187,12 +142,7 @@ const FormAlertCondition = (props) => {
setOperatorState({ ...operatorState, [`op${i}`]: value });
}}
>
<Option value="equals">equals</Option>
<Option value="gte">gte</Option>
<Option value="gt">gt</Option>
<Option value="lt">lt</Option>
<Option value="lte">lte</Option>
<Option value="range">range</Option>
{ operators.map((item) => <Option key={item} value={item}>{item}</Option>)}
</Select>
)}
</Form.Item>
@ -367,6 +317,326 @@ const FormAlertCondition = (props) => {
})}
</div>
);
};
}
export default FormAlertCondition;
const BucketsDiff = (props) => {
const { getFieldDecorator } = props.form;
const alertObjectIndex = props.alertObjectIndex || 0;
const conditions = props.bucketConditions || {};
const [conditionItems, setConditionItems] = useState(conditions?.items || [{ type: 'size' }]);
return (
<div className="group-wrapper">
{conditionItems.map((conditionItem, i) => {
return (
<div key={i}>
<InputGroup compact>
<Form.Item>
{getFieldDecorator(
`alert_objects[${alertObjectIndex}][bucket_conditions][items][${i}][type]`,
{
initialValue: conditionItem.type || "size",
}
)(
<Select style={{ width: 110 }} onChange={(value) => {
const newItems = cloneDeep(conditionItems)
newItems[i].type = value
if (value === 'content') {
newItems[i].values = undefined
newItems[i].operator = undefined
}
setConditionItems(newItems)
}}>
<Option value={'size'}>{formatMessage({id: `alert.rule.form.label.size`})}</Option>
<Option value={'content'}>{formatMessage({id: `alert.rule.form.label.content`})}</Option>
</Select>
)}
</Form.Item>
<Form.Item>
<Input
style={{
width: 40,
textAlign: "center",
pointerEvents: "none",
backgroundColor: "#fafafa",
color: "rgba(0, 0, 0, 0.65)",
}}
defaultValue={formatMessage({
id: `alert.rule.form.label.in`,
})}
disabled
/>
</Form.Item>
<Form.Item>
{getFieldDecorator(
`alert_objects[${alertObjectIndex}][bucket_conditions][items][${i}][bucket_count]`,
{
initialValue: conditionItem.bucket_count || 10,
rules: [
{
required: true,
message: "Please select period!",
},
],
}
)(
<InputNumber style={{ width: 60 }} min={2} max={50} precision={0} step={1}/>
)}
</Form.Item>
<Form.Item>
<Input
style={{
width: 100,
textAlign: "center",
pointerEvents: "none",
backgroundColor: "#fafafa",
color: "rgba(0, 0, 0, 0.65)",
}}
defaultValue={formatMessage({
id: `alert.rule.form.label.stat_period`,
})}
disabled
/>
</Form.Item>
<Form.Item>
{getFieldDecorator(
`alert_objects[${alertObjectIndex}][bucket_conditions][items][${i}][minimum_period_match]`,
{
initialValue: conditionItem.minimum_period_match || 1,
rules: [
{
required: true,
message: "Please select periods match!",
},
],
}
)(
<Select
allowClear
showSearch
style={{ width: 140 }}
placeholder={formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: 1 }
)}
onChange={(value) => {
props.onPreviewChartChange();
}}
>
{
lastsPeriods.map((item) => (
<Option key={item} value={item}>
{formatMessage(
{
id: "alert.rule.form.label.lasts_periods",
},
{ num: item }
)}
</Option>
))
}
</Select>
)}
</Form.Item>
<>
<Form.Item>
{getFieldDecorator(
`alert_objects[${alertObjectIndex}][bucket_conditions][items][${i}][operator]`,
{
initialValue: conditionItem.operator,
rules: [
{
required: true,
message: "Please select operator!",
},
],
}
)(
<Select
allowClear
showSearch
style={{ width: 80 }}
placeholder={"equals"}
onChange={(value) => {
props.onPreviewChartChange();
const newItems = cloneDeep(conditionItems)
newItems[i].operator = value
setConditionItems(newItems)
}}
>
{ operators.map((item) => <Option key={item} value={item}>{item}</Option>)}
</Select>
)}
</Form.Item>
{conditionItem.operator === "range" ? (
<>
<Form.Item>
{getFieldDecorator(
`alert_objects[${alertObjectIndex}][bucket_conditions][items][${i}][values][0]`,
{
initialValue: conditionItem.values?.[0],
rules: [
{
required: true,
message: "Please input min value!",
},
],
}
)(
<Input
style={{ width: 80 }}
placeholder="min value"
onChange={(e) => {
props.onPreviewChartChange();
}}
/>
)}
</Form.Item>
<span
style={{
display: "inline-block",
lineHeight: "40px",
textAlign: "center",
}}
>
<Icon type="minus" />
</span>
<Form.Item>
{getFieldDecorator(
`alert_objects[${alertObjectIndex}][bucket_conditions][items][${i}][values][1]`,
{
initialValue: conditionItem.values?.[1],
rules: [
{
required: true,
message: "Please input max value!",
},
],
}
)(
<Input
style={{ width: 80 }}
placeholder="max value"
onChange={(e) => {
props.onPreviewChartChange();
}}
/>
)}
</Form.Item>
</>
) : (
<Form.Item>
{getFieldDecorator(
`alert_objects[${alertObjectIndex}][bucket_conditions][items][${i}][values][0]`,
{
initialValue: conditionItem.values?.[0],
rules: [
{
required: true,
message: "Please input value!",
},
],
}
)(
<Input
style={{ width: 80 }}
placeholder="value"
onChange={(e) => {
props.onPreviewChartChange();
}}
/>
)}
</Form.Item>
)}
</>
<Form.Item>
<Input
style={{
width: 80,
textAlign: "center",
pointerEvents: "none",
backgroundColor: "#fafafa",
color: "rgba(0, 0, 0, 0.65)",
}}
defaultValue={formatMessage({
id: "alert.rule.form.label.trigger",
})}
disabled
/>
</Form.Item>
<Form.Item>
{getFieldDecorator(
`alert_objects[${alertObjectIndex}][bucket_conditions][items][${i}][priority]`,
{
initialValue: conditionItem.priority,
rules: [
{
required: true,
message: "Please select priority!",
},
],
}
)(
<Select
allowClear
showSearch
style={{ width: 120 }}
placeholder={"P1(High)"}
onChange={(value) => {
props.onPreviewChartChange();
}}
>
{Object.keys(PriorityColor).map((item) => {
return (
<Option key={item} value={item}>
{formatMessage({
id: `alert.message.priority.${item}`,
})}
</Option>
);
})}
</Select>
)}
</Form.Item>
{conditionItems.length > 1 && i > 0 ? (
<Icon
className="dynamic-delete-button"
type="close-circle"
onClick={() => {
setConditionItems(conditionItems.filter((_, key) => key !== i));
}}
/>
) : null}
{i == 0 ? (
<Form.Item>
<Button
type="primary"
icon="plus"
onClick={() => {
if (conditionItems.length >= 5) {
return;
}
setConditionItems([...conditionItems, { type: 'size' }]);
}}
size="small"
style={{ marginLeft: 10 }}
disabled={conditionItems.length >= 5 ? true : false}
>
{formatMessage({
id: "alert.rule.form.button.add_condition",
})}
</Button>
</Form.Item>
) : null}
</InputGroup>
</div>
)
})}
</div>
);
}

View File

@ -106,7 +106,9 @@ const FormAlertObject = (props) => {
statPeriod={props.statPeriod}
/>
</Form.Item>
<Form.Item label="Bucket Label Template">
<Form.Item label={formatMessage({
id: "alert.rule.form.label.bucket_label_template",
})}>
<FormBucketLabel form={props.form} alertObjectIndex={i} initialValue={item?.metrics?.bucket_label || {}} />
</Form.Item>
<Form.Item
@ -118,7 +120,8 @@ const FormAlertObject = (props) => {
<FormAlertCondition
form={props.form}
alertObjectIndex={i}
conditions={item?.conditions || {}}
conditions={item?.conditions}
bucketConditions={item?.bucket_conditions}
onPreviewChartChange={props.onPreviewChartChange}
/>
</Form.Item>