This is an automated email from the ASF dual-hosted git repository.
gongchao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/master by this push:
new 40c842b0d7 [improve] add group convergence strategy name validation
(#4050)
40c842b0d7 is described below
commit 40c842b0d795bd1df1e324684a5fad1e6e496ace
Author: Duansg <[email protected]>
AuthorDate: Wed Mar 4 23:30:01 2026 +0800
[improve] add group convergence strategy name validation (#4050)
Co-authored-by: Tomsun28 <[email protected]>
---
.../hertzbeat/alert/dao/AlertGroupConvergeDao.java | 9 +-
.../impl/AlertGroupConvergeServiceImpl.java | 33 ++++--
.../src/main/resources/alerter_en_US.properties | 2 +
.../src/main/resources/alerter_zh_CN.properties | 2 +
.../src/main/resources/alerter_zh_TW.properties | 2 +
.../impl/AlertGroupConvergeServiceImplTest.java | 115 +++++++++++++++++++++
.../common/entity/alerter/AlertGroupConverge.java | 14 ++-
.../alert-group-converge.component.html | 4 +-
.../alert-group/alert-group-converge.component.ts | 4 +-
web-app/src/assets/i18n/en-US.json | 2 +-
web-app/src/assets/i18n/ja-JP.json | 2 +-
web-app/src/assets/i18n/pt-BR.json | 2 +-
web-app/src/assets/i18n/zh-CN.json | 2 +-
web-app/src/assets/i18n/zh-TW.json | 2 +-
14 files changed, 171 insertions(+), 24 deletions(-)
diff --git
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertGroupConvergeDao.java
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertGroupConvergeDao.java
index b8ca5648e7..e74f12d659 100644
---
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertGroupConvergeDao.java
+++
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertGroupConvergeDao.java
@@ -41,5 +41,12 @@ public interface AlertGroupConvergeDao extends
JpaRepository<AlertGroupConverge,
* @return group alarm converge list
*/
List<AlertGroupConverge> findAlertGroupConvergesByEnableIsTrue();
-
+
+ /**
+ * Query group alarm converge list based on the name
+ * @param name alert converge name
+ * @return group alarm converge list
+ */
+ List<AlertGroupConverge> findAlertGroupConvergesByName(String name);
+
}
diff --git
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertGroupConvergeServiceImpl.java
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertGroupConvergeServiceImpl.java
index 7fbdcd4837..c09df759d9 100644
---
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertGroupConvergeServiceImpl.java
+++
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertGroupConvergeServiceImpl.java
@@ -21,12 +21,14 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
+import java.util.ResourceBundle;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dao.AlertGroupConvergeDao;
import org.apache.hertzbeat.alert.reduce.AlarmGroupReduce;
import org.apache.hertzbeat.alert.service.AlertGroupConvergeService;
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
+import org.apache.hertzbeat.common.util.ResourceBundleUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@@ -43,41 +45,52 @@ import org.springframework.util.StringUtils;
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class AlertGroupConvergeServiceImpl implements
AlertGroupConvergeService {
-
+
@Autowired
private AlertGroupConvergeDao alertGroupConvergeDao;
-
+
@Autowired
private AlarmGroupReduce alarmGroupReduce;
-
+
+ protected ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
+
@Override
public void validate(AlertGroupConverge alertGroupConverge, boolean
isModify) throws IllegalArgumentException {
- // todo
+ if (alertGroupConverge == null ||
!StringUtils.hasText(alertGroupConverge.getName())) {
+ throw new
IllegalArgumentException(bundle.getString("alerter.converge.empty.name"));
+ }
+ List<AlertGroupConverge> sameNameConverges =
alertGroupConvergeDao.findAlertGroupConvergesByName(alertGroupConverge.getName());
+ if (sameNameConverges != null && !sameNameConverges.isEmpty()) {
+ boolean isDuplicate = sameNameConverges.stream().anyMatch(it ->
!it.getId().equals(alertGroupConverge.getId()));
+ if (isDuplicate) {
+ throw new
IllegalArgumentException(bundle.getString("alerter.converge.duplicate.name"));
+ }
+ }
}
-
+
@Override
public void addAlertGroupConverge(AlertGroupConverge alertGroupConverge)
throws RuntimeException {
alertGroupConvergeDao.save(alertGroupConverge);
refreshAlertGroupConvergesCache();
}
-
+
@Override
public void modifyAlertGroupConverge(AlertGroupConverge
alertGroupConverge) throws RuntimeException {
alertGroupConvergeDao.save(alertGroupConverge);
refreshAlertGroupConvergesCache();
}
-
+
@Override
public AlertGroupConverge getAlertGroupConverge(long convergeId) throws
RuntimeException {
return alertGroupConvergeDao.findById(convergeId).orElse(null);
}
-
+
@Override
public void deleteAlertGroupConverges(Set<Long> convergeIds) throws
RuntimeException {
alertGroupConvergeDao.deleteAlertGroupConvergesByIdIn(convergeIds);
refreshAlertGroupConvergesCache();
}
-
+
@Override
public Page<AlertGroupConverge> getAlertGroupConverges(List<Long>
convergeIds, String search, String sort, String order, int pageIndex, int
pageSize) {
Specification<AlertGroupConverge> specification = (root, query,
criteriaBuilder) -> {
@@ -105,7 +118,7 @@ public class AlertGroupConvergeServiceImpl implements
AlertGroupConvergeService
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sortExp);
return alertGroupConvergeDao.findAll(specification, pageRequest);
}
-
+
private void refreshAlertGroupConvergesCache() {
List<AlertGroupConverge> alertGroupConverges =
alertGroupConvergeDao.findAlertGroupConvergesByEnableIsTrue();
alarmGroupReduce.refreshGroupDefines(alertGroupConverges);
diff --git a/hertzbeat-alerter/src/main/resources/alerter_en_US.properties
b/hertzbeat-alerter/src/main/resources/alerter_en_US.properties
index bd7851d06e..6c30e1f26b 100644
--- a/hertzbeat-alerter/src/main/resources/alerter_en_US.properties
+++ b/hertzbeat-alerter/src/main/resources/alerter_en_US.properties
@@ -34,3 +34,5 @@ alerter.priority.1 = Critical Alert
alerter.priority.2 = Warning Alert
alerter.calculate.parse.error = Expression is not fully parsed, may have
syntax errors or incomplete inputs
alerter.datasource.executor.not.found = No query executor found
+alerter.converge.duplicate.name = Group convergence strategy name already
exists
+alerter.converge.empty.name = Group convergence strategy name cannot be empty
diff --git a/hertzbeat-alerter/src/main/resources/alerter_zh_CN.properties
b/hertzbeat-alerter/src/main/resources/alerter_zh_CN.properties
index bc4398b918..3e307a3334 100644
--- a/hertzbeat-alerter/src/main/resources/alerter_zh_CN.properties
+++ b/hertzbeat-alerter/src/main/resources/alerter_zh_CN.properties
@@ -34,3 +34,5 @@ alerter.priority.1 = 严重告警
alerter.priority.2 = 警告告警
alerter.calculate.parse.error = 表达式未完全解析,可能存在语法错误或输入不完整
alerter.datasource.executor.not.found = 未找到查询执行器
+alerter.converge.duplicate.name = 分组收敛策略名称已存在
+alerter.converge.empty.name = 分组收敛策略名称不能为空
diff --git a/hertzbeat-alerter/src/main/resources/alerter_zh_TW.properties
b/hertzbeat-alerter/src/main/resources/alerter_zh_TW.properties
index 6878bf06f6..8e765eb655 100644
--- a/hertzbeat-alerter/src/main/resources/alerter_zh_TW.properties
+++ b/hertzbeat-alerter/src/main/resources/alerter_zh_TW.properties
@@ -34,3 +34,5 @@ alerter.priority.1 = 嚴重警報
alerter.priority.2 = 警告警報
alerter.calculate.parse.error = 表達式未完全解析,可能存在語法錯誤或輸入不完整
alerter.datasource.executor.not.found = 未找到查詢執行器
+alerter.converge.duplicate.name = 分組收斂策略名稱已存在
+alerter.converge.empty.name = 分組收斂策略名稱不能為空
diff --git
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/AlertGroupConvergeServiceImplTest.java
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/AlertGroupConvergeServiceImplTest.java
new file mode 100644
index 0000000000..a0739e225a
--- /dev/null
+++
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/AlertGroupConvergeServiceImplTest.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.when;
+import java.util.Collections;
+import java.util.List;
+import org.apache.hertzbeat.alert.dao.AlertGroupConvergeDao;
+import org.apache.hertzbeat.alert.reduce.AlarmGroupReduce;
+import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * Test case for {@link AlertGroupConvergeServiceImpl}
+ */
+@ExtendWith(MockitoExtension.class)
+public class AlertGroupConvergeServiceImplTest {
+
+ @Mock
+ private AlertGroupConvergeDao alertGroupConvergeDao;
+
+ @Mock
+ private AlarmGroupReduce alarmGroupReduce;
+
+ @InjectMocks
+ private AlertGroupConvergeServiceImpl alertGroupConvergeService;
+
+ private AlertGroupConverge converge;
+
+ @BeforeEach
+ void setUp() {
+ converge = AlertGroupConverge.builder()
+ .id(1L)
+ .name("test-converge")
+ .build();
+ }
+
+ @Test
+ void testValidateNewSuccess() {
+ converge.setId(null);
+
when(alertGroupConvergeDao.findAlertGroupConvergesByName("test-converge"))
+ .thenReturn(Collections.emptyList());
+ assertDoesNotThrow(() -> alertGroupConvergeService.validate(converge,
false));
+ }
+
+ @Test
+ void testValidateNewDuplicateName() {
+ converge.setId(null);
+ AlertGroupConverge existing = AlertGroupConverge.builder()
+ .id(2L)
+ .name("test-converge")
+ .build();
+
when(alertGroupConvergeDao.findAlertGroupConvergesByName("test-converge"))
+ .thenReturn(List.of(existing));
+ assertThrows(IllegalArgumentException.class, () ->
alertGroupConvergeService.validate(converge, false));
+ }
+
+ @Test
+ void testValidateNewEmptyName() {
+ converge.setName("");
+ assertThrows(IllegalArgumentException.class, () ->
alertGroupConvergeService.validate(converge, false));
+
+ converge.setName(null);
+ assertThrows(IllegalArgumentException.class, () ->
alertGroupConvergeService.validate(converge, false));
+ }
+
+ @Test
+ void testValidateModifySuccessNameUnchanged() {
+
when(alertGroupConvergeDao.findAlertGroupConvergesByName("test-converge"))
+ .thenReturn(List.of(converge));
+ assertDoesNotThrow(() -> alertGroupConvergeService.validate(converge,
true));
+ }
+
+ @Test
+ void testValidateModifySuccessNameChanged() {
+ converge.setName("new-name");
+ when(alertGroupConvergeDao.findAlertGroupConvergesByName("new-name"))
+ .thenReturn(Collections.emptyList());
+ assertDoesNotThrow(() -> alertGroupConvergeService.validate(converge,
true));
+ }
+
+ @Test
+ void testValidateModifyDuplicateName() {
+ converge.setName("existing-name");
+ AlertGroupConverge existing = AlertGroupConverge.builder()
+ .id(2L)
+ .name("existing-name")
+ .build();
+
when(alertGroupConvergeDao.findAlertGroupConvergesByName("existing-name"))
+ .thenReturn(List.of(existing));
+ assertThrows(IllegalArgumentException.class, () ->
alertGroupConvergeService.validate(converge, true));
+ }
+}
diff --git
a/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/alerter/AlertGroupConverge.java
b/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/alerter/AlertGroupConverge.java
index 423ba43692..0debf14968 100644
---
a/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/alerter/AlertGroupConverge.java
+++
b/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/alerter/AlertGroupConverge.java
@@ -25,6 +25,7 @@ import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
+import jakarta.persistence.Index;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@@ -45,7 +46,9 @@ import
org.springframework.data.jpa.domain.support.AuditingEntityListener;
* Alert group converge strategy entity
*/
@Entity
-@Table(name = "hzb_alert_group_converge")
+@Table(name = "hzb_alert_group_converge", indexes = {
+ @Index(name = "idx_name", columnList = "name")
+})
@Data
@Builder
@AllArgsConstructor
@@ -62,21 +65,22 @@ public class AlertGroupConverge {
@Schema(title = "Policy name", example = "group-converge-1")
@Size(max = 100)
@NotNull
+ @Column(name = "name")
private String name;
-
+
@Schema(title = "Labels to group by", example = "[\"instance\"]")
@Convert(converter = JsonStringListAttributeConverter.class)
@Column(name = "group_labels", length = 1024)
private List<String> groupLabels;
-
+
@Schema(title = "Initial wait time before sending first group alert (s)",
example = "30")
@Column(name = "group_wait")
private Long groupWait;
-
+
@Schema(title = "Interval between group alert sends (s)", example = "300")
@Column(name = "group_interval")
private Long groupInterval;
-
+
@Schema(title = "Interval for repeating firing alerts (s), set to 0 to
disable repeating", example = "9000")
@Column(name = "repeat_interval")
private Long repeatInterval;
diff --git
a/web-app/src/app/routes/alert/alert-group/alert-group-converge.component.html
b/web-app/src/app/routes/alert/alert-group/alert-group-converge.component.html
index c1e59c8946..98cc59f498 100644
---
a/web-app/src/app/routes/alert/alert-group/alert-group-converge.component.html
+++
b/web-app/src/app/routes/alert/alert-group/alert-group-converge.component.html
@@ -98,7 +98,9 @@
<span>{{ data.name }}</span>
</td>
<td nzAlign="center">
- <nz-tag *ngFor="let label of data.groupLabels">{{ label }}</nz-tag>
+ <nz-tag *ngFor="let label of data.groupLabels" nz-tooltip
[nzTooltipTitle]="label.length > 15 ? label : null">
+ {{ label.length > 15 ? (label | slice : 0 : 15) + '...' : label }}
+ </nz-tag>
</td>
<td nzAlign="center"> {{ data.groupWait }}{{
'alert.group-converge.seconds' | i18n }} </td>
<td nzAlign="center"> {{ data.groupInterval }}{{
'alert.group-converge.seconds' | i18n }} </td>
diff --git
a/web-app/src/app/routes/alert/alert-group/alert-group-converge.component.ts
b/web-app/src/app/routes/alert/alert-group/alert-group-converge.component.ts
index 8d67bdbe05..b6b6eed9b7 100644
--- a/web-app/src/app/routes/alert/alert-group/alert-group-converge.component.ts
+++ b/web-app/src/app/routes/alert/alert-group/alert-group-converge.component.ts
@@ -210,7 +210,7 @@ export class AlertGroupConvergeComponent implements OnInit {
onNewGroupConverge() {
this.groupConverge = new AlertGroupConverge();
- this.groupConverge.groupLabels = [''];
+ this.groupConverge.groupLabels = [];
this.isManageModalAdd = true;
this.isManageModalVisible = true;
this.isManageModalOkLoading = false;
@@ -234,7 +234,7 @@ export class AlertGroupConvergeComponent implements OnInit {
if (message.code === 0) {
this.groupConverge = message.data;
if (!Array.isArray(this.groupConverge.groupLabels) ||
this.groupConverge.groupLabels.length === 0) {
- this.groupConverge.groupLabels = [''];
+ this.groupConverge.groupLabels = [];
}
this.isManageModalVisible = true;
} else {
diff --git a/web-app/src/assets/i18n/en-US.json
b/web-app/src/assets/i18n/en-US.json
index 56a715772d..15990e38ed 100644
--- a/web-app/src/assets/i18n/en-US.json
+++ b/web-app/src/assets/i18n/en-US.json
@@ -83,7 +83,7 @@
"alert.inhibit.equal_labels.common": "Common Labels",
"alert.inhibit.equal_labels.custom": "Custom Labels",
"alert.inhibit.equal_labels.more": "{{count}} more labels",
- "alert.inhibit.equal_labels.placeholder": "Press Enter after entering label
name, or select from dropdown",
+ "alert.inhibit.equal_labels.placeholder": "Input or select labels",
"alert.inhibit.equal_labels.tip": "Source and target alerts must have equal
values for these label keys, common keys like alertname, instance, severity
etc",
"alert.inhibit.name": "Inhibit Rule Name",
"alert.inhibit.name.tip": "Name to identify this inhibit rule, must be
unique",
diff --git a/web-app/src/assets/i18n/ja-JP.json
b/web-app/src/assets/i18n/ja-JP.json
index ed2648d9ff..66ec1addfe 100644
--- a/web-app/src/assets/i18n/ja-JP.json
+++ b/web-app/src/assets/i18n/ja-JP.json
@@ -83,7 +83,7 @@
"alert.inhibit.equal_labels.common": "共通ラベル",
"alert.inhibit.equal_labels.custom": "カスタムラベル",
"alert.inhibit.equal_labels.more": "{{count}} 個のラベルが追加",
- "alert.inhibit.equal_labels.placeholder":
"ラベル名を入力後、Enterキーを押すか、ドロップダウンから選択してください",
+ "alert.inhibit.equal_labels.placeholder": "ラベルを入力または選択",
"alert.inhibit.equal_labels.tip":
"ソースおよびターゲットのアラートは、これらのラベルキーに対して等しい値を持つ必要があります。共通キーとしてalertname、instance、severityなどがあります",
"alert.inhibit.name": "抑制ルール名",
"alert.inhibit.name.tip": "この抑制ルールを識別するための名前。ユニークでなければなりません",
diff --git a/web-app/src/assets/i18n/pt-BR.json
b/web-app/src/assets/i18n/pt-BR.json
index 8b255db6f2..dd460551b4 100644
--- a/web-app/src/assets/i18n/pt-BR.json
+++ b/web-app/src/assets/i18n/pt-BR.json
@@ -305,7 +305,7 @@
"alert.inhibit.equal_labels.common": "Tags comuns",
"alert.inhibit.equal_labels.custom": "Tags personalizadas",
"alert.inhibit.equal_labels.more": "Existem também {{count}} tags",
- "alert.inhibit.equal_labels.placeholder": "Digite o nome da tag e pressione
Enter ou selecione na lista suspensa",
+ "alert.inhibit.equal_labels.placeholder": "Insira ou selecione tags",
"alert.inhibit.equal_labels.tip": "As chaves de tag e os valores
correspondentes dos alarmes de origem e dos alarmes de destino devem ser
iguais. As chaves de tag comuns incluem alertname, instância, gravidade, etc.",
"alert.inhibit.name": "Suprimir nome da regra",
"alert.inhibit.name.tip": "Um nome que identifique esta regra de supressão
precisa ser exclusivo",
diff --git a/web-app/src/assets/i18n/zh-CN.json
b/web-app/src/assets/i18n/zh-CN.json
index 4636cb055e..5f6e4bcb7c 100644
--- a/web-app/src/assets/i18n/zh-CN.json
+++ b/web-app/src/assets/i18n/zh-CN.json
@@ -83,7 +83,7 @@
"alert.inhibit.equal_labels.common": "常用标签",
"alert.inhibit.equal_labels.custom": "自定义标签",
"alert.inhibit.equal_labels.more": "还有 {{count}} 个标签",
- "alert.inhibit.equal_labels.placeholder": "输入标签名称后按回车,或从下拉列表选择",
+ "alert.inhibit.equal_labels.placeholder": "输入或选择标签",
"alert.inhibit.equal_labels.tip": "源告警和目标告警这些标签Key和对应值必须相等,常用标签Key如
alertname、instance、severity 等",
"alert.inhibit.name": "抑制规则名称",
"alert.inhibit.name.tip": "标识此抑制规则的名称,需要唯一",
diff --git a/web-app/src/assets/i18n/zh-TW.json
b/web-app/src/assets/i18n/zh-TW.json
index 211027a20e..2b4fbc57cb 100644
--- a/web-app/src/assets/i18n/zh-TW.json
+++ b/web-app/src/assets/i18n/zh-TW.json
@@ -83,7 +83,7 @@
"alert.inhibit.equal_labels.common": "常用標籤",
"alert.inhibit.equal_labels.custom": "自定義標籤",
"alert.inhibit.equal_labels.more": "還有 {{count}} 個標籤",
- "alert.inhibit.equal_labels.placeholder": "輸入標籤名稱後按回車,或從下拉列表選擇",
+ "alert.inhibit.equal_labels.placeholder": "輸入或選擇標籤",
"alert.inhibit.equal_labels.tip": "源告警和目標告警這些標籤Key和對應值必須相等,常用標籤Key如
alertname、instance、severity 等",
"alert.inhibit.name": "抑制規則名稱",
"alert.inhibit.name.tip": "標識此抑制規則的名稱,需要唯一",
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]