This is an automated email from the ASF dual-hosted git repository.
cyyang 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 a19a399acd [bugfix&improve] Fix alert label persistence and improve
custom label input (#3933)
a19a399acd is described below
commit a19a399acd193c9575cf14ae7e74a3d072d86966
Author: P_Peaceful <[email protected]>
AuthorDate: Tue Jan 6 12:01:53 2026 +0800
[bugfix&improve] Fix alert label persistence and improve custom label input
(#3933)
---
.../alert/service/impl/AlertDefineServiceImpl.java | 40 +++++++++--
.../alert/service/AlertDefineServiceTest.java | 78 ++++++++++++----------
.../org/apache/hertzbeat/base}/dao/LabelDao.java | 6 +-
.../hertzbeat/base}/service/LabelService.java | 2 +-
.../manager/controller/LabelController.java | 4 +-
.../service/impl/AbstractImExportServiceImpl.java | 2 +-
.../manager/service/impl/LabelServiceImpl.java | 4 +-
.../manager/service/impl/MonitorServiceImpl.java | 4 +-
.../manager/service/LabelServiceTest.java | 3 +-
.../manager/service/MonitorServiceTest.java | 1 +
.../apache/hertzbeat/startup/dao/LabelDaoTest.java | 2 +-
.../alert-setting/alert-setting.component.html | 3 +
.../alert/alert-setting/alert-setting.component.ts | 1 +
.../label-selector/label-selector.component.html | 16 ++++-
.../label-selector/label-selector.component.ts | 75 ++++++++++++++++++++-
15 files changed, 187 insertions(+), 54 deletions(-)
diff --git
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineServiceImpl.java
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineServiceImpl.java
index 16b2376fe8..22340def60 100644
---
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineServiceImpl.java
+++
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineServiceImpl.java
@@ -29,11 +29,14 @@ import org.apache.hertzbeat.alert.dao.AlertDefineDao;
import org.apache.hertzbeat.alert.service.AlertDefineImExportService;
import org.apache.hertzbeat.alert.service.AlertDefineService;
import org.apache.hertzbeat.alert.service.DataSourceService;
+import org.apache.hertzbeat.base.dao.LabelDao;
+import org.apache.hertzbeat.base.service.LabelService;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.constants.ExportFileConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+import org.apache.hertzbeat.common.entity.manager.Label;
import org.apache.hertzbeat.common.util.FileUtil;
import org.apache.hertzbeat.common.util.JexlExpressionRunner;
import org.springframework.beans.factory.annotation.Autowired;
@@ -48,6 +51,7 @@ import
org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
+import javax.annotation.Resource;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@@ -58,6 +62,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Alarm definition management interface implementation
@@ -69,10 +74,16 @@ public class AlertDefineServiceImpl implements
AlertDefineService {
@Autowired
private AlertDefineDao alertDefineDao;
-
+
@Autowired
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
+ @Resource
+ private LabelService labelService;
+
+ @Resource
+ private LabelDao labelDao;
+
private final DataSourceService dataSourceService;
private final Map<String, AlertDefineImExportService>
alertDefineImExportServiceMap = new HashMap<>();
@@ -93,7 +104,7 @@ public class AlertDefineServiceImpl implements
AlertDefineService {
JexlExpressionRunner.compile(alertDefine.getExpr());
} catch (Exception e) {
throw new IllegalArgumentException("alert expr error: " +
e.getMessage());
- }
+ }
}
}
// the name of the alarm rule is unique
@@ -107,6 +118,7 @@ public class AlertDefineServiceImpl implements
AlertDefineService {
@Override
public void addAlertDefine(AlertDefine alertDefine) throws
RuntimeException {
+ saveNewCustomLabel(alertDefine);
alertDefine = alertDefineDao.saveAndFlush(alertDefine);
periodicAlertRuleScheduler.updateSchedule(alertDefine);
CacheFactory.clearAlertDefineCache();
@@ -114,11 +126,31 @@ public class AlertDefineServiceImpl implements
AlertDefineService {
@Override
public void modifyAlertDefine(AlertDefine alertDefine) throws
RuntimeException {
+ saveNewCustomLabel(alertDefine);
alertDefineDao.saveAndFlush(alertDefine);
periodicAlertRuleScheduler.updateSchedule(alertDefine);
CacheFactory.clearAlertDefineCache();
}
+ private void saveNewCustomLabel(AlertDefine alertDefine) {
+ Map<String, String> labels = alertDefine.getLabels();
+ if (labels == null) {
+ labels = new HashMap<>(8);
+ alertDefine.setLabels(labels);
+ }
+ Map<String, String> customLabels = labels.entrySet().stream()
+ .filter(entry -> !isSystemBuiltInLabel(entry.getKey()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ List<Label> addLabels =
labelService.determineNewLabels(customLabels.entrySet());
+ if (!addLabels.isEmpty()) {
+ labelDao.saveAll(addLabels);
+ }
+ }
+
+ private boolean isSystemBuiltInLabel(String labelKey) {
+ return CommonConstants.ALERT_MODE_LABEL.equals(labelKey) ||
CommonConstants.LABEL_ALERT_SEVERITY.contains(labelKey);
+ }
+
@Override
public void deleteAlertDefine(long alertId) throws RuntimeException {
alertDefineDao.deleteById(alertId);
@@ -256,7 +288,7 @@ public class AlertDefineServiceImpl implements
AlertDefineService {
if (!StringUtils.hasText(type)) {
throw new IllegalArgumentException("Alert definition type cannot
be null or empty");
}
-
+
switch (type) {
case CommonConstants.METRIC_ALERT_THRESHOLD_TYPE_REALTIME:
case CommonConstants.METRIC_ALERT_THRESHOLD_TYPE_PERIODIC:
@@ -267,7 +299,7 @@ public class AlertDefineServiceImpl implements
AlertDefineService {
default:
throw new IllegalArgumentException("Unsupported alert
definition type: " + type);
}
-
+
// Query enabled alert definitions by type
return alertDefineDao.findAlertDefinesByTypeAndEnableTrue(type);
}
diff --git
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/AlertDefineServiceTest.java
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/AlertDefineServiceTest.java
index 4f40c17ded..de29d2ccd5 100644
---
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/AlertDefineServiceTest.java
+++
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/AlertDefineServiceTest.java
@@ -21,6 +21,8 @@ import com.google.common.collect.Lists;
import
org.apache.hertzbeat.alert.calculate.periodic.PeriodicAlertRuleScheduler;
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
import org.apache.hertzbeat.alert.service.impl.AlertDefineServiceImpl;
+import org.apache.hertzbeat.base.dao.LabelDao;
+import org.apache.hertzbeat.base.service.LabelService;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.junit.jupiter.api.BeforeEach;
@@ -69,7 +71,13 @@ class AlertDefineServiceTest {
@Mock
private AlertDefineDao alertDefineDao;
-
+
+ @Mock
+ private LabelService labelService;
+
+ @Mock
+ private LabelDao labelDao;
+
@Mock
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
@@ -86,6 +94,8 @@ class AlertDefineServiceTest {
void setUp() {
ReflectionTestUtils.setField(this.alertDefineService,
"alertDefineDao", alertDefineDao);
ReflectionTestUtils.setField(this.alertDefineService,
"periodicAlertRuleScheduler", periodicAlertRuleScheduler);
+ ReflectionTestUtils.setField(this.alertDefineService, "labelService",
labelService);
+ ReflectionTestUtils.setField(this.alertDefineService, "labelDao",
labelDao);
this.alertDefine = AlertDefine.builder()
.id(1L)
@@ -187,40 +197,40 @@ class AlertDefineServiceTest {
AlertDefine.builder().id(1L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build(),
AlertDefine.builder().id(2L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
);
-
+
try (MockedStatic<CacheFactory> cacheFactoryMock =
Mockito.mockStatic(CacheFactory.class)) {
// Mock cache hit
cacheFactoryMock.when(CacheFactory::getMetricsAlertDefineCache).thenReturn(cachedAlertDefines);
-
+
List<AlertDefine> result =
alertDefineService.getMetricsRealTimeAlertDefines();
-
+
assertNotNull(result);
assertEquals(2, result.size());
assertEquals(1L, result.get(0).getId());
assertEquals(2L, result.get(1).getId());
-
+
// Verify no database query was called
verify(alertDefineDao,
times(0)).findAlertDefinesByTypeAndEnableTrue(any());
cacheFactoryMock.verify(() ->
CacheFactory.setMetricsAlertDefineCache(any()), times(0));
}
-
+
// Test cache miss scenario
List<AlertDefine> dbAlertDefines = Lists.newArrayList(
AlertDefine.builder().id(3L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
);
-
+
try (MockedStatic<CacheFactory> cacheFactoryMock =
Mockito.mockStatic(CacheFactory.class)) {
// Mock cache miss
cacheFactoryMock.when(CacheFactory::getMetricsAlertDefineCache).thenReturn(null);
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME))
.thenReturn(dbAlertDefines);
-
+
List<AlertDefine> result =
alertDefineService.getMetricsRealTimeAlertDefines();
-
+
assertNotNull(result);
assertEquals(1, result.size());
assertEquals(3L, result.get(0).getId());
-
+
// Verify database query and cache setting were called
verify(alertDefineDao,
times(1)).findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME);
cacheFactoryMock.verify(() ->
CacheFactory.setMetricsAlertDefineCache(dbAlertDefines), times(1));
@@ -234,40 +244,40 @@ class AlertDefineServiceTest {
AlertDefine.builder().id(4L).type(LOG_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build(),
AlertDefine.builder().id(5L).type(LOG_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
);
-
+
try (MockedStatic<CacheFactory> cacheFactoryMock =
Mockito.mockStatic(CacheFactory.class)) {
// Mock cache hit
cacheFactoryMock.when(CacheFactory::getLogAlertDefineCache).thenReturn(cachedAlertDefines);
-
+
List<AlertDefine> result =
alertDefineService.getLogRealTimeAlertDefines();
-
+
assertNotNull(result);
assertEquals(2, result.size());
assertEquals(4L, result.get(0).getId());
assertEquals(5L, result.get(1).getId());
-
+
// Verify no database query was called
verify(alertDefineDao,
times(0)).findAlertDefinesByTypeAndEnableTrue(any());
cacheFactoryMock.verify(() ->
CacheFactory.setLogAlertDefineCache(any()), times(0));
}
-
+
// Test cache miss scenario
List<AlertDefine> dbAlertDefines = Lists.newArrayList(
AlertDefine.builder().id(6L).type(LOG_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
);
-
+
try (MockedStatic<CacheFactory> cacheFactoryMock =
Mockito.mockStatic(CacheFactory.class)) {
// Mock cache miss
cacheFactoryMock.when(CacheFactory::getLogAlertDefineCache).thenReturn(null);
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME))
.thenReturn(dbAlertDefines);
-
+
List<AlertDefine> result =
alertDefineService.getLogRealTimeAlertDefines();
-
+
assertNotNull(result);
assertEquals(1, result.size());
assertEquals(6L, result.get(0).getId());
-
+
// Verify database query and cache setting were called
verify(alertDefineDao,
times(1)).findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME);
cacheFactoryMock.verify(() ->
CacheFactory.setLogAlertDefineCache(dbAlertDefines), times(1));
@@ -280,58 +290,58 @@ class AlertDefineServiceTest {
AlertDefine.builder().id(7L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build(),
AlertDefine.builder().id(8L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
);
-
+
// Test valid metric realtime alert type
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME))
.thenReturn(mockAlertDefines);
-
+
List<AlertDefine> result =
alertDefineService.getAlertDefinesByType(METRIC_ALERT_THRESHOLD_TYPE_REALTIME);
-
+
assertNotNull(result);
assertEquals(2, result.size());
assertEquals(7L, result.get(0).getId());
assertEquals(8L, result.get(1).getId());
verify(alertDefineDao,
times(1)).findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME);
-
+
// Test valid metric periodic alert type
reset(alertDefineDao);
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC))
.thenReturn(Lists.newArrayList());
-
+
result =
alertDefineService.getAlertDefinesByType(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC);
-
+
assertNotNull(result);
assertEquals(0, result.size());
verify(alertDefineDao,
times(1)).findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC);
-
+
// Test valid log realtime alert type
reset(alertDefineDao);
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME))
.thenReturn(mockAlertDefines);
-
+
result =
alertDefineService.getAlertDefinesByType(LOG_ALERT_THRESHOLD_TYPE_REALTIME);
-
+
assertNotNull(result);
assertEquals(2, result.size());
verify(alertDefineDao,
times(1)).findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME);
-
+
// Test valid log periodic alert type
reset(alertDefineDao);
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_PERIODIC))
.thenReturn(Lists.newArrayList());
-
+
result =
alertDefineService.getAlertDefinesByType(LOG_ALERT_THRESHOLD_TYPE_PERIODIC);
-
+
assertNotNull(result);
assertEquals(0, result.size());
verify(alertDefineDao,
times(1)).findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_PERIODIC);
-
+
// Test empty string type
assertThrows(IllegalArgumentException.class, () ->
alertDefineService.getAlertDefinesByType(""));
-
+
// Test null type
assertThrows(IllegalArgumentException.class, () ->
alertDefineService.getAlertDefinesByType(null));
-
+
// Test invalid type
assertThrows(IllegalArgumentException.class, () ->
alertDefineService.getAlertDefinesByType("invalid_type"));
}
diff --git
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/dao/LabelDao.java
b/hertzbeat-base/src/main/java/org/apache/hertzbeat/base/dao/LabelDao.java
similarity index 96%
rename from
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/dao/LabelDao.java
rename to
hertzbeat-base/src/main/java/org/apache/hertzbeat/base/dao/LabelDao.java
index d1498e58e8..372e75acb3 100644
---
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/dao/LabelDao.java
+++ b/hertzbeat-base/src/main/java/org/apache/hertzbeat/base/dao/LabelDao.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.hertzbeat.manager.dao;
+package org.apache.hertzbeat.base.dao;
import java.util.Optional;
import java.util.Set;
@@ -33,7 +33,7 @@ public interface LabelDao extends JpaRepository<Label, Long>,
JpaSpecificationEx
* @param ids id list
*/
void deleteLabelsByIdIn(Set<Long> ids);
-
+
/**
* find Label by name and value
* @param name Label name
@@ -41,5 +41,5 @@ public interface LabelDao extends JpaRepository<Label, Long>,
JpaSpecificationEx
* @return Label
*/
Optional<Label> findLabelByNameAndTagValue(String name, String value);
-
+
}
diff --git
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/LabelService.java
b/hertzbeat-base/src/main/java/org/apache/hertzbeat/base/service/LabelService.java
similarity index 97%
rename from
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/LabelService.java
rename to
hertzbeat-base/src/main/java/org/apache/hertzbeat/base/service/LabelService.java
index 1b24dc279b..5de8b7e540 100644
---
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/LabelService.java
+++
b/hertzbeat-base/src/main/java/org/apache/hertzbeat/base/service/LabelService.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.hertzbeat.manager.service;
+package org.apache.hertzbeat.base.service;
import java.util.HashSet;
import java.util.List;
diff --git
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/LabelController.java
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/LabelController.java
index 6588cd44ee..eba5c55a66 100644
---
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/LabelController.java
+++
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/LabelController.java
@@ -26,7 +26,7 @@ import java.util.HashSet;
import java.util.List;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.apache.hertzbeat.common.entity.manager.Label;
-import org.apache.hertzbeat.manager.service.LabelService;
+import org.apache.hertzbeat.base.service.LabelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
@@ -46,7 +46,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(path = "/api/label", produces = {APPLICATION_JSON_VALUE})
public class LabelController {
-
+
@Autowired
private LabelService labelService;
diff --git
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AbstractImExportServiceImpl.java
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AbstractImExportServiceImpl.java
index 89277f36c6..7bf85944de 100644
---
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AbstractImExportServiceImpl.java
+++
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AbstractImExportServiceImpl.java
@@ -31,7 +31,7 @@ import org.apache.hertzbeat.manager.config.ManagerSseManager;
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.service.ImExportService;
import org.apache.hertzbeat.manager.service.MonitorService;
-import org.apache.hertzbeat.manager.service.LabelService;
+import org.apache.hertzbeat.base.service.LabelService;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.CollectionUtils;
diff --git
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/LabelServiceImpl.java
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/LabelServiceImpl.java
index 127bcc892b..a8c63637ae 100644
---
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/LabelServiceImpl.java
+++
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/LabelServiceImpl.java
@@ -31,8 +31,8 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.entity.manager.Label;
-import org.apache.hertzbeat.manager.dao.LabelDao;
-import org.apache.hertzbeat.manager.service.LabelService;
+import org.apache.hertzbeat.base.dao.LabelDao;
+import org.apache.hertzbeat.base.service.LabelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
diff --git
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java
index a1d903e4b4..4c97f91432 100644
---
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java
+++
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java
@@ -50,7 +50,7 @@ import org.apache.hertzbeat.grafana.service.DashboardService;
import org.apache.hertzbeat.manager.component.validator.ParamValidatorManager;
import org.apache.hertzbeat.manager.dao.CollectorDao;
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
-import org.apache.hertzbeat.manager.dao.LabelDao;
+import org.apache.hertzbeat.base.dao.LabelDao;
import org.apache.hertzbeat.manager.dao.MonitorBindDao;
import org.apache.hertzbeat.manager.dao.MonitorDao;
import org.apache.hertzbeat.manager.dao.ParamDao;
@@ -59,7 +59,7 @@ import org.apache.hertzbeat.manager.pojo.dto.MetricsInfo;
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.scheduler.CollectJobScheduling;
import org.apache.hertzbeat.manager.service.AppService;
-import org.apache.hertzbeat.manager.service.LabelService;
+import org.apache.hertzbeat.base.service.LabelService;
import org.apache.hertzbeat.manager.service.MetricsFavoriteService;
import org.apache.hertzbeat.manager.service.MonitorService;
import org.apache.hertzbeat.manager.service.helper.MonitorImExportHelper;
diff --git
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/LabelServiceTest.java
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/LabelServiceTest.java
index a99d564f74..29e2d2924d 100644
---
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/LabelServiceTest.java
+++
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/LabelServiceTest.java
@@ -17,6 +17,7 @@
package org.apache.hertzbeat.manager.service;
+import org.apache.hertzbeat.base.service.LabelService;
import org.junit.jupiter.api.Assertions;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -34,7 +35,7 @@ import java.util.Map;
import java.util.Optional;
import org.apache.hertzbeat.common.entity.manager.Label;
-import org.apache.hertzbeat.manager.dao.LabelDao;
+import org.apache.hertzbeat.base.dao.LabelDao;
import org.apache.hertzbeat.manager.service.impl.LabelServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
diff --git
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/MonitorServiceTest.java
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/MonitorServiceTest.java
index a48293a3c0..1a20aaad34 100644
---
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/MonitorServiceTest.java
+++
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/MonitorServiceTest.java
@@ -35,6 +35,7 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import org.apache.hertzbeat.alert.dao.AlertDefineBindDao;
+import org.apache.hertzbeat.base.service.LabelService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
diff --git
a/hertzbeat-startup/src/test/java/org/apache/hertzbeat/startup/dao/LabelDaoTest.java
b/hertzbeat-startup/src/test/java/org/apache/hertzbeat/startup/dao/LabelDaoTest.java
index 9c57ad3f38..b8619010ca 100644
---
a/hertzbeat-startup/src/test/java/org/apache/hertzbeat/startup/dao/LabelDaoTest.java
+++
b/hertzbeat-startup/src/test/java/org/apache/hertzbeat/startup/dao/LabelDaoTest.java
@@ -27,7 +27,7 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.hertzbeat.common.entity.manager.Label;
-import org.apache.hertzbeat.manager.dao.LabelDao;
+import org.apache.hertzbeat.base.dao.LabelDao;
import org.apache.hertzbeat.startup.AbstractSpringIntegrationTest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
diff --git
a/web-app/src/app/routes/alert/alert-setting/alert-setting.component.html
b/web-app/src/app/routes/alert/alert-setting/alert-setting.component.html
index 224e315a56..751dc4bf61 100644
--- a/web-app/src/app/routes/alert/alert-setting/alert-setting.component.html
+++ b/web-app/src/app/routes/alert/alert-setting/alert-setting.component.html
@@ -910,6 +910,9 @@
field: 'labels',
type: 'label-selector'
}"
+ [extra]="{
+ labelIsCustom: labelIsCustom
+ }"
[name]="'labels'"
[(ngModel)]="define.labels"
/>
diff --git
a/web-app/src/app/routes/alert/alert-setting/alert-setting.component.ts
b/web-app/src/app/routes/alert/alert-setting/alert-setting.component.ts
index 8e51fc531e..bb6725af8c 100644
--- a/web-app/src/app/routes/alert/alert-setting/alert-setting.component.ts
+++ b/web-app/src/app/routes/alert/alert-setting/alert-setting.component.ts
@@ -577,6 +577,7 @@ export class AlertSettingComponent implements OnInit {
userExpr!: string;
severity!: string;
alertMode!: string;
+ labelIsCustom: boolean = true;
logFields: any[] = [];
editAlertDefine(alertDefineId: number) {
diff --git
a/web-app/src/app/shared/components/label-selector/label-selector.component.html
b/web-app/src/app/shared/components/label-selector/label-selector.component.html
index 0e34b7161b..c0fcc9a284 100644
---
a/web-app/src/app/shared/components/label-selector/label-selector.component.html
+++
b/web-app/src/app/shared/components/label-selector/label-selector.component.html
@@ -32,7 +32,13 @@
>
<nz-option *ngFor="let keyOption of labelKeys" [nzLabel]="keyOption"
[nzValue]="keyOption"></nz-option>
<nz-option
- *ngIf="labelIsCustom && customInputKey &&
!hasMatchingOption(customInputKey, labelKeys)"
+ *ngIf="
+ labelIsCustom &&
+ activeSearchIndex === i &&
+ activeSearchType === 'key' &&
+ customInputKey &&
+ !hasMatchingOption(customInputKey, labelKeys)
+ "
[nzLabel]="customInputKey"
[nzValue]="customInputKey"
></nz-option>
@@ -53,7 +59,13 @@
>
<nz-option *ngFor="let valueOption of getLabelValues(value[i].key)"
[nzLabel]="valueOption" [nzValue]="valueOption"></nz-option>
<nz-option
- *ngIf="labelIsCustom && customInputValue &&
!hasMatchingOption(customInputValue, getLabelValues(value[i].key))"
+ *ngIf="
+ labelIsCustom &&
+ activeSearchIndex === i &&
+ activeSearchType === 'value' &&
+ customInputValue &&
+ !hasMatchingOption(customInputValue, getLabelValues(value[i].key))
+ "
[nzLabel]="customInputValue"
[nzValue]="customInputValue"
></nz-option>
diff --git
a/web-app/src/app/shared/components/label-selector/label-selector.component.ts
b/web-app/src/app/shared/components/label-selector/label-selector.component.ts
index 031be73dfd..1d4275bcab 100644
---
a/web-app/src/app/shared/components/label-selector/label-selector.component.ts
+++
b/web-app/src/app/shared/components/label-selector/label-selector.component.ts
@@ -46,32 +46,105 @@ export class LabelSelectorComponent implements
ControlValueAccessor, OnInit {
value: Array<{ key: string; value: string }> = [];
customInputKey: string = '';
customInputValue: string = '';
+ // Track the active search input to prevent cross-row pollution of custom
options
+ activeSearchIndex: number | null = null;
+ activeSearchType: 'key' | 'value' | null = null;
_onChange = (_: any) => {};
_onTouched = () => {};
writeValue(value: any): void {
- this.value = value && value.length > 0 ? value : [{ key: '', value: '' }];
+ // Validate and initialize: use valid array if provided, otherwise
initialize with empty key-value pair
+ if (!value || !Array.isArray(value) || value.length === 0) {
+ this.value = [{ key: '', value: '' }];
+ return;
+ }
+ this.value = value;
+ // Build labelKeys and labelMap for autocomplete from the provided values
+ this.value.forEach(item => {
+ // Skip invalid items
+ if (!item || typeof item !== 'object' || !item.key) {
+ return;
+ }
+
+ const key = String(item.key).trim();
+ const val = item.value ? String(item.value).trim() : '';
+ // Skip empty keys
+ if (!key) {
+ return;
+ }
+ // Add new key to labelKeys
+ if (!this.labelKeys.includes(key)) {
+ this.labelKeys.push(key);
+ }
+ // Initialize value array for this key
+ if (!this.labelMap[key]) {
+ this.labelMap[key] = [];
+ }
+ // Add value to labelMap (with deduplication)
+ if (val && !this.labelMap[key].includes(val)) {
+ this.labelMap[key].push(val);
+ }
+ });
}
+ /**
+ * Handle key change: clear the corresponding value and add new key to
labelKeys for reuse
+ */
onKeyChange(index: number, value: string) {
+ // Clear the value when key changes
this.value[index].value = '';
+
+ // Add new key to labelKeys so other rows can see this custom key
+ if (value && !this.labelKeys.includes(value)) {
+ this.labelKeys.push(value);
+ }
+
+ // Initialize value array for this key
+ if (value && !this.labelMap[value]) {
+ this.labelMap[value] = [];
+ }
+
this._onChange(this.value);
this._onTouched();
}
+ /**
+ * Handle value change: add new value to the corresponding key's labelMap
for reuse
+ */
onValueChange(index: number, value: string) {
+ const key = this.value[index].key;
+
+ // Add new value to labelMap so other rows can see this custom value
+ if (key && value && this.labelMap[key] &&
!this.labelMap[key].includes(value)) {
+ this.labelMap[key].push(value);
+ }
+
this._onChange(this.value);
this._onTouched();
}
+ /**
+ * Handle search event: track the active input to ensure custom options only
show in the corresponding field
+ */
onSearch(value: string, index: number, type: 'key' | 'value'): void {
+ // Track the currently active search input
+ this.activeSearchIndex = index;
+ this.activeSearchType = type;
+
if (value) {
if (type === 'key') {
this.customInputKey = value;
} else {
this.customInputValue = value;
}
+ } else {
+ // Clear custom input value when search is empty
+ if (type === 'key') {
+ this.customInputKey = '';
+ } else {
+ this.customInputValue = '';
+ }
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]