From c464ba993ff633b0c882bd2aef1d782500f91fad Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Tue, 28 Oct 2025 13:12:16 +0200 Subject: [PATCH 1/9] Improve alarm template --- .../AlarmAssignmentTriggerProcessor.java | 1 + .../trigger/AlarmCommentTriggerProcessor.java | 5 +++- .../rule/trigger/AlarmTriggerProcessor.java | 17 ++++++++++++ .../server/controller/AbstractWebTest.java | 11 +++++--- .../notification/NotificationRuleApiTest.java | 24 ++++++++--------- .../server/common/data/alarm/AlarmInfo.java | 8 ++++-- .../common/data/device/profile/AlarmRule.java | 4 +++ .../info/AlarmAssignmentNotificationInfo.java | 4 ++- .../info/AlarmCommentNotificationInfo.java | 4 ++- .../info/AlarmNotificationInfo.java | 26 ++++++++++--------- .../rule/trigger/AlarmCommentTrigger.java | 5 ++++ .../rule/trigger/AlarmTrigger.java | 5 ++++ ...signmentNotificationRuleTriggerConfig.java | 4 +++ ...mCommentNotificationRuleTriggerConfig.java | 4 +++ .../AlarmNotificationRuleTriggerConfig.java | 6 +++++ .../thingsboard/server/common/msg/TbMsg.java | 13 ++++------ .../assets/help/en_US/notification/alarm.md | 2 ++ .../en_US/notification/alarm_assignment.md | 1 + .../help/en_US/notification/alarm_comment.md | 1 + 19 files changed, 105 insertions(+), 40 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java index ca3667b6ef..f9646409c6 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java @@ -62,6 +62,7 @@ public class AlarmAssignmentTriggerProcessor implements NotificationRuleTriggerP .alarmType(alarmInfo.getType()) .alarmOriginator(alarmInfo.getOriginator()) .alarmOriginatorName(alarmInfo.getOriginatorName()) + .alarmOriginatorLabel(alarmInfo.getOriginatorLabel()) .alarmSeverity(alarmInfo.getSeverity()) .alarmStatus(alarmInfo.getStatus()) .alarmCustomerId(alarmInfo.getCustomerId()) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java index 8cca98f7fd..691be2e894 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java @@ -57,11 +57,13 @@ public class AlarmCommentTriggerProcessor implements NotificationRuleTriggerProc @Override public RuleOriginatedNotificationInfo constructNotificationInfo(AlarmCommentTrigger trigger) { Alarm alarm = trigger.getAlarm(); - String originatorName; + String originatorName, originatorLabel; if (alarm instanceof AlarmInfo) { originatorName = ((AlarmInfo) alarm).getOriginatorName(); + originatorLabel = ((AlarmInfo) alarm).getOriginatorLabel(); } else { originatorName = entityService.fetchEntityName(trigger.getTenantId(), alarm.getOriginator()).orElse(""); + originatorLabel = entityService.fetchEntityLabel(trigger.getTenantId(), alarm.getOriginator()).orElse(""); } return AlarmCommentNotificationInfo.builder() .comment(trigger.getComment().getComment().get("text").asText()) @@ -73,6 +75,7 @@ public class AlarmCommentTriggerProcessor implements NotificationRuleTriggerProc .alarmType(alarm.getType()) .alarmOriginator(alarm.getOriginator()) .alarmOriginatorName(originatorName) + .alarmOriginatorLabel(originatorLabel) .alarmSeverity(alarm.getSeverity()) .alarmStatus(alarm.getStatus()) .alarmCustomerId(alarm.getCustomerId()) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java index 7d6959dc4f..af80d7f2ab 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java @@ -15,7 +15,9 @@ */ package org.thingsboard.server.service.notification.rule.trigger; +import com.fasterxml.jackson.databind.JsonNode; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -28,6 +30,9 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Alarm import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmNotificationRuleTriggerConfig.ClearRule; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; +import java.util.HashMap; +import java.util.Map; + import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains; @@ -106,15 +111,27 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor toInfoTemplateMap(JsonNode details) { + Map infoMap = JacksonUtil.toFlatMap(details); + Map result = new HashMap<>(); + for (Map.Entry entry : infoMap.entrySet()) { + String key = "info." + entry.getKey(); + result.put(key, entry.getValue()); + } + return result; + } + @Override public NotificationRuleTriggerType getTriggerType() { return NotificationRuleTriggerType.ALARM; diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index fcbe203e79..f04cc049c8 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -680,9 +680,14 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { } protected Device createDevice(String name, String accessToken) throws Exception { + return createDevice(name, "default", null, accessToken); + } + + protected Device createDevice(String name, String type, String label, String accessToken) throws Exception { Device device = new Device(); device.setName(name); - device.setType("default"); + device.setType(type); + device.setLabel(label); DeviceData deviceData = new DeviceData(); deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration()); deviceData.setConfiguration(new DefaultDeviceConfiguration()); @@ -1117,7 +1122,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { Awaitility.await("CF state for entity actor ready to refresh dynamic arguments").atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> { CalculatedFieldState calculatedFieldState = statesMap.get(cfId); boolean isReady = calculatedFieldState != null && ((GeofencingCalculatedFieldState) calculatedFieldState).getLastDynamicArgumentsRefreshTs() - < System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(scheduledUpdateInterval); + < System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(scheduledUpdateInterval); log.warn("entityId {}, cfId {}, state ready to refresh == {}", entityId, cfId, isReady); return isReady; }); @@ -1308,7 +1313,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { protected List findJobs(List types, List entities) throws Exception { return doGetTypedWithPageLink("/api/jobs?types=" + types.stream().map(Enum::name).collect(Collectors.joining(",")) + - "&entities=" + entities.stream().map(UUID::toString).collect(Collectors.joining(",")) + "&", + "&entities=" + entities.stream().map(UUID::toString).collect(Collectors.joining(",")) + "&", new TypeReference>() {}, new PageLink(100, 0, null, new SortOrder("createdTime", SortOrder.Direction.DESC))).getData(); } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index bab70ea505..10eff284f8 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -22,9 +22,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.function.ThrowingRunnable; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.data.util.Pair; import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.common.data.DataConstants; @@ -137,7 +137,7 @@ import static org.thingsboard.server.common.data.notification.rule.trigger.confi }) public class NotificationRuleApiTest extends AbstractNotificationApiTest { - @SpyBean + @MockitoSpyBean private AlarmSubscriptionService alarmSubscriptionService; @Autowired private DefaultSystemInfoService systemInfoService; @@ -193,7 +193,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { @Test public void testNotificationRuleProcessing_alarmTrigger() throws Exception { String notificationSubject = "Alarm type: ${alarmType}, status: ${alarmStatus}, " + - "severity: ${alarmSeverity}, deviceId: ${alarmOriginatorId}"; + "severity: ${alarmSeverity}, deviceId: ${alarmOriginatorId}, details: ${data}."; String notificationText = "Status: ${alarmStatus}, severity: ${alarmSeverity}"; NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, notificationSubject, notificationText, NotificationDeliveryMethod.WEB); @@ -221,12 +221,12 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { clients.put(delay, userAndClient.getSecond()); } notificationRule.setRecipientsConfig(recipientsConfig); - notificationRule = saveNotificationRule(notificationRule); + saveNotificationRule(notificationRule); String alarmType = "myBoolIsTrue"; DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(alarmType); - Device device = createDevice("Device 1", deviceProfile.getName(), "1234"); + Device device = createDevice("Device 1", deviceProfile.getName(), "label", "1234"); clients.values().forEach(wsClient -> { wsClient.subscribeForUnreadNotifications(10).waitForReply(true); @@ -250,7 +250,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { assertThat(actualDelay).isCloseTo(expectedDelay, offset(2.0)); assertThat(notification.getSubject()).isEqualTo("Alarm type: " + alarmType + ", status: " + AlarmStatus.ACTIVE_UNACK + ", " + - "severity: " + AlarmSeverity.CRITICAL.toString().toLowerCase() + ", deviceId: " + device.getId()); + "severity: " + AlarmSeverity.CRITICAL.toString().toLowerCase() + ", deviceId: " + device.getId() + ", details: attribute is true."); assertThat(notification.getText()).isEqualTo("Status: " + AlarmStatus.ACTIVE_UNACK + ", severity: " + AlarmSeverity.CRITICAL.toString().toLowerCase()); assertThat(notification.getType()).isEqualTo(NotificationType.ALARM); @@ -270,7 +270,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { wsClient.waitForUpdate(true); Notification updatedNotification = wsClient.getLastDataUpdate().getUpdate(); assertThat(updatedNotification.getSubject()).isEqualTo("Alarm type: " + alarmType + ", status: " + expectedStatus + ", " + - "severity: " + expectedSeverity.toString().toLowerCase() + ", deviceId: " + device.getId()); + "severity: " + expectedSeverity.toString().toLowerCase() + ", deviceId: " + device.getId() + ", details: attribute is true."); assertThat(updatedNotification.getText()).isEqualTo("Status: " + expectedStatus + ", severity: " + expectedSeverity.toString().toLowerCase()); wsClient.close(); @@ -516,10 +516,10 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { .notifyOn(Set.of(ASSIGNED, UNASSIGNED)) .build(); NotificationTarget target = createNotificationTarget(tenantAdminUserId); - String template = "${userEmail} ${action} alarm on ${alarmOriginatorEntityType} '${alarmOriginatorName}'. Assignee: ${assigneeEmail}"; + String template = "${userEmail} ${action} alarm on ${alarmOriginatorEntityType} '${alarmOriginatorName}' with label '${alarmOriginatorLabel}'. Assignee: ${assigneeEmail}"; createNotificationRule(triggerConfig, "Test", template, target.getId()); - Device device = createDevice("Device A", "123"); + Device device = createDevice("Device A", "default", "test", "123"); Alarm alarm = Alarm.builder() .tenantId(tenantId) .originator(device.getId()) @@ -536,7 +536,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { doPost("/api/alarm/" + alarmId + "/assign/" + tenantAdminUserId).andExpect(status().isOk()); }, notification -> { assertThat(notification.getText()).isEqualTo( - TENANT_ADMIN_EMAIL + " assigned alarm on Device 'Device A'. Assignee: " + TENANT_ADMIN_EMAIL + TENANT_ADMIN_EMAIL + " assigned alarm on Device 'Device A' with label 'test'. Assignee: " + TENANT_ADMIN_EMAIL ); }); @@ -544,7 +544,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { doDelete("/api/alarm/" + alarmId + "/assign").andExpect(status().isOk()); }, notification -> { assertThat(notification.getText()).isEqualTo( - TENANT_ADMIN_EMAIL + " unassigned alarm on Device 'Device A'. Assignee: " + TENANT_ADMIN_EMAIL + " unassigned alarm on Device 'Device A' with label 'test'. Assignee: " ); }); } @@ -950,7 +950,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { alarm.setAlarmType(alarmType); alarm.setId(alarmType); AlarmRule alarmRule = new AlarmRule(); - alarmRule.setAlarmDetails("Details"); + alarmRule.setAlarmDetails("attribute is ${bool}"); AlarmCondition alarmCondition = new AlarmCondition(); alarmCondition.setSpec(new SimpleAlarmConditionSpec()); List condition = new ArrayList<>(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index b316a2ef50..ee168a2e0b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java @@ -21,11 +21,14 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import java.io.Serial; + @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) @Schema public class AlarmInfo extends Alarm { + @Serial private static final long serialVersionUID = 2807343093519543363L; @Getter @@ -58,9 +61,10 @@ public class AlarmInfo extends Alarm { public AlarmInfo(AlarmInfo alarmInfo) { super(alarmInfo); - this.originatorName = alarmInfo.originatorName; - this.originatorLabel = alarmInfo.originatorLabel; + this.originatorName = alarmInfo.getOriginatorName(); + this.originatorLabel = alarmInfo.getOriginatorLabel(); this.assignee = alarmInfo.getAssignee(); + this.originatorDisplayName = alarmInfo.getOriginatorDisplayName(); } public AlarmInfo(Alarm alarm, String originatorName, String originatorLabel, AlarmAssignee assignee) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java index 16850e3669..faeecd7eb3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java @@ -21,12 +21,16 @@ import lombok.Data; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.validation.NoXss; +import java.io.Serial; import java.io.Serializable; @Schema @Data public class AlarmRule implements Serializable { + @Serial + private static final long serialVersionUID = -7617427132423304707L; + @Valid @Schema(description = "JSON object representing the alarm rule condition") private AlarmCondition condition; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java index f29a54d17a..086b326ddd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java @@ -53,6 +53,7 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati private UUID alarmId; private EntityId alarmOriginator; private String alarmOriginatorName; + private String alarmOriginatorLabel; private AlarmSeverity alarmSeverity; private AlarmStatus alarmStatus; private CustomerId alarmCustomerId; @@ -77,7 +78,8 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati "alarmStatus", alarmStatus.toString(), "alarmOriginatorEntityType", alarmOriginator.getEntityType().getNormalName(), "alarmOriginatorId", alarmOriginator.getId().toString(), - "alarmOriginatorName", alarmOriginatorName + "alarmOriginatorName", alarmOriginatorName, + "alarmOriginatorLabel", alarmOriginatorLabel ); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java index e46d539399..202812921f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java @@ -48,6 +48,7 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI private UUID alarmId; private EntityId alarmOriginator; private String alarmOriginatorName; + private String alarmOriginatorLabel; private AlarmSeverity alarmSeverity; private AlarmStatus alarmStatus; private CustomerId alarmCustomerId; @@ -68,7 +69,8 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI "alarmStatus", alarmStatus.toString(), "alarmOriginatorEntityType", alarmOriginator.getEntityType().getNormalName(), "alarmOriginatorId", alarmOriginator.getId().toString(), - "alarmOriginatorName", alarmOriginatorName + "alarmOriginatorName", alarmOriginatorName, + "alarmOriginatorLabel", alarmOriginatorLabel ); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java index a1a9a34a36..cfcf66b016 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java @@ -25,11 +25,10 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.EntityId; +import java.util.HashMap; import java.util.Map; import java.util.UUID; -import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf; - @Data @NoArgsConstructor @AllArgsConstructor @@ -41,25 +40,28 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo { private UUID alarmId; private EntityId alarmOriginator; private String alarmOriginatorName; + private String alarmOriginatorLabel; private AlarmSeverity alarmSeverity; private AlarmStatus alarmStatus; private boolean acknowledged; private boolean cleared; private CustomerId alarmCustomerId; private DashboardId dashboardId; + private Map info; @Override public Map getTemplateData() { - return mapOf( - "alarmType", alarmType, - "action", action, - "alarmId", alarmId.toString(), - "alarmSeverity", alarmSeverity.name().toLowerCase(), - "alarmStatus", alarmStatus.toString(), - "alarmOriginatorEntityType", alarmOriginator.getEntityType().getNormalName(), - "alarmOriginatorName", alarmOriginatorName, - "alarmOriginatorId", alarmOriginator.getId().toString() - ); + Map templateData = new HashMap<>(info); + templateData.put("alarmType", alarmType); + templateData.put("action", action); + templateData.put("alarmId", alarmId.toString()); + templateData.put("alarmSeverity", alarmSeverity.name().toLowerCase()); + templateData.put("alarmStatus", alarmStatus.toString()); + templateData.put("alarmOriginatorEntityType", alarmOriginator.getEntityType().getNormalName()); + templateData.put("alarmOriginatorName", alarmOriginatorName); + templateData.put("alarmOriginatorLabel", alarmOriginatorLabel); + templateData.put("alarmOriginatorId", alarmOriginator.getId().toString()); + return templateData; } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentTrigger.java index a9cee05707..e6298691ea 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentTrigger.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentTrigger.java @@ -25,10 +25,15 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; +import java.io.Serial; + @Data @Builder public class AlarmCommentTrigger implements NotificationRuleTrigger { + @Serial + private static final long serialVersionUID = -8614770559491757202L; + private final TenantId tenantId; private final AlarmComment comment; private final Alarm alarm; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmTrigger.java index a64bf13266..275c3b1e18 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmTrigger.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmTrigger.java @@ -22,10 +22,15 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; +import java.io.Serial; + @Data @Builder public class AlarmTrigger implements NotificationRuleTrigger { + @Serial + private static final long serialVersionUID = -466810297904938644L; + private final TenantId tenantId; private final AlarmApiCallResult alarmUpdate; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmAssignmentNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmAssignmentNotificationRuleTriggerConfig.java index a63a2d0cdf..cf701b5221 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmAssignmentNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmAssignmentNotificationRuleTriggerConfig.java @@ -23,6 +23,7 @@ import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import java.io.Serial; import java.util.Set; @Data @@ -31,6 +32,9 @@ import java.util.Set; @Builder public class AlarmAssignmentNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { + @Serial + private static final long serialVersionUID = -5313556049809972096L; + private Set alarmTypes; private Set alarmSeverities; private Set alarmStatuses; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmCommentNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmCommentNotificationRuleTriggerConfig.java index e2da4f5291..62762cce6e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmCommentNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmCommentNotificationRuleTriggerConfig.java @@ -22,6 +22,7 @@ import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import java.io.Serial; import java.util.Set; @Data @@ -30,6 +31,9 @@ import java.util.Set; @Builder public class AlarmCommentNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { + @Serial + private static final long serialVersionUID = -9164282098882339645L; + private Set alarmTypes; private Set alarmSeverities; private Set alarmStatuses; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmNotificationRuleTriggerConfig.java index 53f160b8ad..5006e63abd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/AlarmNotificationRuleTriggerConfig.java @@ -23,6 +23,7 @@ import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import java.io.Serial; import java.io.Serializable; import java.util.Set; @@ -32,6 +33,9 @@ import java.util.Set; @Builder public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { + @Serial + private static final long serialVersionUID = -7382883720381542344L; + private Set alarmTypes; private Set alarmSeverities; @NotEmpty @@ -46,6 +50,8 @@ public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTrigg @Data public static class ClearRule implements Serializable { + @Serial + private static final long serialVersionUID = 7922533150038105124L; private Set alarmStatuses; } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index 1d8d9497a9..7c9f51a3ef 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -41,9 +41,6 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; -/** - * Created by ashvayka on 13.01.18. - */ @Data @Slf4j public final class TbMsg implements Serializable { @@ -500,11 +497,11 @@ public final class TbMsg implements Serializable { public String toString() { return "TbMsg.TbMsgBuilder(queueName=" + this.queueName + ", id=" + this.id + ", ts=" + this.ts + - ", type=" + this.type + ", internalType=" + this.internalType + ", originator=" + this.originator + - ", customerId=" + this.customerId + ", metaData=" + this.metaData + ", dataType=" + this.dataType + - ", data=" + this.data + ", ruleChainId=" + this.ruleChainId + ", ruleNodeId=" + this.ruleNodeId + - ", correlationId=" + this.correlationId + ", partition=" + this.partition + ", previousCalculatedFields=" + this.previousCalculatedFieldIds + - ", ctx=" + this.ctx + ", callback=" + this.callback + ")"; + ", type=" + this.type + ", internalType=" + this.internalType + ", originator=" + this.originator + + ", customerId=" + this.customerId + ", metaData=" + this.metaData + ", dataType=" + this.dataType + + ", data=" + this.data + ", ruleChainId=" + this.ruleChainId + ", ruleNodeId=" + this.ruleNodeId + + ", correlationId=" + this.correlationId + ", partition=" + this.partition + ", previousCalculatedFields=" + this.previousCalculatedFieldIds + + ", ctx=" + this.ctx + ", callback=" + this.callback + ")"; } } diff --git a/ui-ngx/src/assets/help/en_US/notification/alarm.md b/ui-ngx/src/assets/help/en_US/notification/alarm.md index 00ba021e1b..b03addf7a1 100644 --- a/ui-ngx/src/assets/help/en_US/notification/alarm.md +++ b/ui-ngx/src/assets/help/en_US/notification/alarm.md @@ -16,11 +16,13 @@ Available template parameters: * `alarmStatus` - the alarm status; * `alarmOriginatorEntityType` - the entity type of the alarm originator, e.g. 'Device'; * `alarmOriginatorName` - the name of the alarm originator, e.g. 'Sensor T1'; +* `alarmOriginatorLabel` - the label of the alarm originator, e.g. 'Sensor T1'; * `alarmOriginatorId` - the alarm originator entity id as uuid string; * `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); * `recipientEmail` - email of the recipient; * `recipientFirstName` - first name of the recipient; * `recipientLastName` - last name of the recipient; +* `info.` - any key field from the alarm's additional info. Fox example, if additional info is `{"data": "Temperature is 25"}`, use `${info.data}` to access "Temperature is 25"; Parameter names must be wrapped using `${...}`. For example: `${action}`. You may also modify the value of the parameter with one of the suffixes: diff --git a/ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md b/ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md index aa80b13b35..53b2ade36d 100644 --- a/ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md +++ b/ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md @@ -15,6 +15,7 @@ Available template parameters: * `alarmStatus` - the alarm status; * `alarmOriginatorEntityType` - the entity type of the alarm originator, e.g. 'Device'; * `alarmOriginatorName` - the name of the alarm originator, e.g. 'Sensor T1'; +* `alarmOriginatorLabel` - the label of the alarm originator, e.g. 'Sensor T1'; * `alarmOriginatorId` - the alarm originator entity id as uuid string; * `assigneeTitle` - title of the assignee; * `assigneeEmail` - email of the assignee; diff --git a/ui-ngx/src/assets/help/en_US/notification/alarm_comment.md b/ui-ngx/src/assets/help/en_US/notification/alarm_comment.md index 150e76babc..41ada1fba2 100644 --- a/ui-ngx/src/assets/help/en_US/notification/alarm_comment.md +++ b/ui-ngx/src/assets/help/en_US/notification/alarm_comment.md @@ -15,6 +15,7 @@ Available template parameters: * `alarmStatus` - the alarm status; * `alarmOriginatorEntityType` - the entity type of the alarm originator, e.g. 'Device'; * `alarmOriginatorName` - the name of the alarm originator, e.g. 'Sensor T1'; +* `alarmOriginatorLabel` - the label of the alarm originator, e.g. 'Sensor T1'; * `alarmOriginatorId` - the alarm originator entity id as uuid string; * `comment` - text of the comment; * `action` - one of: 'added', 'updated'; From ddfc023da127815e1adc20290bda48825f00f4ca Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Tue, 28 Oct 2025 14:22:32 +0200 Subject: [PATCH 2/9] Fix tests --- .../java/org/thingsboard/server/common/data/alarm/AlarmInfo.java | 1 - 1 file changed, 1 deletion(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index ee168a2e0b..70032327b9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java @@ -64,7 +64,6 @@ public class AlarmInfo extends Alarm { this.originatorName = alarmInfo.getOriginatorName(); this.originatorLabel = alarmInfo.getOriginatorLabel(); this.assignee = alarmInfo.getAssignee(); - this.originatorDisplayName = alarmInfo.getOriginatorDisplayName(); } public AlarmInfo(Alarm alarm, String originatorName, String originatorLabel, AlarmAssignee assignee) { From 18b0b56cc47a8b46fa83269f2b60b73513ef81aa Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Tue, 28 Oct 2025 16:29:27 +0200 Subject: [PATCH 3/9] Fix testNotificationRuleProcessing_alarmTrigger --- .../server/service/notification/NotificationRuleApiTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index 10eff284f8..659f6379e7 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -193,7 +193,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { @Test public void testNotificationRuleProcessing_alarmTrigger() throws Exception { String notificationSubject = "Alarm type: ${alarmType}, status: ${alarmStatus}, " + - "severity: ${alarmSeverity}, deviceId: ${alarmOriginatorId}, details: ${data}."; + "severity: ${alarmSeverity}, deviceId: ${alarmOriginatorId}, details: ${info.data}."; String notificationText = "Status: ${alarmStatus}, severity: ${alarmSeverity}"; NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, notificationSubject, notificationText, NotificationDeliveryMethod.WEB); From 890f19ea931c13be6d2b548dbda60bf99d7af4e6 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Tue, 28 Oct 2025 17:40:28 +0200 Subject: [PATCH 4/9] Fix VC tests --- .../service/sync/vc/VersionControlTest.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java b/application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java index 2db78871d4..73f67739f3 100644 --- a/application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java @@ -244,7 +244,7 @@ public class VersionControlTest extends AbstractControllerTest { DeviceProfile deviceProfile = createDeviceProfile(null, null, "Device profile v1.0"); OtaPackage firmware = createOtaPackage(tenantId1, deviceProfile.getId(), OtaPackageType.FIRMWARE); OtaPackage software = createOtaPackage(tenantId1, deviceProfile.getId(), OtaPackageType.SOFTWARE); - Device device = createDevice(null, deviceProfile.getId(), "Device v1.0", "test1", newDevice -> { + Device device = createDevice(deviceProfile.getId(), "Device v1.0", "test1", newDevice -> { newDevice.setFirmwareId(firmware.getId()); newDevice.setSoftwareId(software.getId()); }); @@ -267,7 +267,7 @@ public class VersionControlTest extends AbstractControllerTest { createVersion("profiles", EntityType.DEVICE_PROFILE); OtaPackage firmware = createOtaPackage(tenantId1, deviceProfile.getId(), OtaPackageType.FIRMWARE); OtaPackage software = createOtaPackage(tenantId1, deviceProfile.getId(), OtaPackageType.SOFTWARE); - Device device = createDevice(null, deviceProfile.getId(), "Device of tenant 1", "test1", newDevice -> { + Device device = createDevice(deviceProfile.getId(), "Device of tenant 1", "test1", newDevice -> { newDevice.setFirmwareId(firmware.getId()); newDevice.setSoftwareId(software.getId()); }); @@ -528,7 +528,7 @@ public class VersionControlTest extends AbstractControllerTest { @Test public void testVcWithRelations_betweenTenants() throws Exception { Asset asset = createAsset(null, null, "Asset 1"); - Device device = createDevice(null, null, "Device 1", "test1"); + Device device = createDevice("Device 1", "test1"); EntityRelation relation = createRelation(asset.getId(), device.getId()); String versionId = createVersion("assets and devices", EntityType.ASSET, EntityType.DEVICE, EntityType.DEVICE_PROFILE, EntityType.ASSET_PROFILE); @@ -554,11 +554,11 @@ public class VersionControlTest extends AbstractControllerTest { @Test public void testVcWithRelations_sameTenant() throws Exception { Asset asset = createAsset(null, null, "Asset 1"); - Device device1 = createDevice(null, null, "Device 1", "test1"); + Device device1 = createDevice("Device 1", "test1"); EntityRelation relation1 = createRelation(device1.getId(), asset.getId()); String versionId = createVersion("assets", EntityType.ASSET); - Device device2 = createDevice(null, null, "Device 2", "test2"); + Device device2 = createDevice("Device 2", "test2"); EntityRelation relation2 = createRelation(device2.getId(), asset.getId()); List relations = findRelationsByTo(asset.getId()); assertThat(relations).contains(relation1, relation2); @@ -591,7 +591,7 @@ public class VersionControlTest extends AbstractControllerTest { @Test public void testVcWithCalculatedFields_betweenTenants() throws Exception { Asset asset = createAsset(null, null, "Asset 1"); - Device device = createDevice(null, null, "Device 1", "test1"); + Device device = createDevice("Device 1", "test1"); CalculatedField calculatedField = createCalculatedField("CalculatedField1", device.getId(), asset.getId()); String versionId = createVersion("calculated fields of asset and device", EntityType.ASSET, EntityType.DEVICE, EntityType.DEVICE_PROFILE, EntityType.ASSET_PROFILE); @@ -617,7 +617,7 @@ public class VersionControlTest extends AbstractControllerTest { @Test public void testVcWithReferencedCalculatedFields_betweenTenants() throws Exception { Asset asset = createAsset(null, null, "Asset 1"); - Device device = createDevice(null, null, "Device 1", "test1"); + Device device = createDevice("Device 1", "test1"); CalculatedField deviceCalculatedField = createCalculatedField("CalculatedField1", device.getId(), asset.getId()); CalculatedField assetCalculatedField = createCalculatedField("CalculatedField2", asset.getId(), device.getId()); String versionId = createVersion("calculated fields of asset and device", EntityType.ASSET, EntityType.DEVICE, EntityType.DEVICE_PROFILE, EntityType.ASSET_PROFILE); @@ -911,9 +911,8 @@ public class VersionControlTest extends AbstractControllerTest { login(tenantAdmin2.getEmail(), tenantAdmin2.getEmail()); } - private Device createDevice(CustomerId customerId, DeviceProfileId deviceProfileId, String name, String accessToken, Consumer... modifiers) { + private Device createDevice(DeviceProfileId deviceProfileId, String name, String accessToken, Consumer... modifiers) { Device device = new Device(); - device.setCustomerId(customerId); device.setName(name); device.setLabel("lbl"); device.setDeviceProfileId(deviceProfileId); From 0de539bfb785d355335f8438b47da241df6efb7a Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Mon, 10 Nov 2025 16:08:46 +0200 Subject: [PATCH 5/9] fixed compilation error in cf msa tests --- .../org/thingsboard/server/msa/cf/CalculatedFieldTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java index 8046e0b1a6..68c2c9462f 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java @@ -449,8 +449,7 @@ public class CalculatedFieldTest extends AbstractContainerTest { cf.setConfigurationVersion(1); PropagationCalculatedFieldConfiguration cfg = new PropagationCalculatedFieldConfiguration(); - cfg.setDirection(EntitySearchDirection.TO); - cfg.setRelationType(EntityRelation.CONTAINS_TYPE); + cfg.setRelation(new RelationPathLevel(EntitySearchDirection.TO, EntityRelation.CONTAINS_TYPE)); cfg.setApplyExpressionToResolvedArguments(true); Argument arg = new Argument(); @@ -535,8 +534,7 @@ public class CalculatedFieldTest extends AbstractContainerTest { cf.setConfigurationVersion(1); PropagationCalculatedFieldConfiguration cfg = new PropagationCalculatedFieldConfiguration(); - cfg.setDirection(EntitySearchDirection.TO); - cfg.setRelationType(EntityRelation.CONTAINS_TYPE); + cfg.setRelation(new RelationPathLevel(EntitySearchDirection.TO, EntityRelation.CONTAINS_TYPE)); cfg.setApplyExpressionToResolvedArguments(false); // arguments-only mode Argument arg = new Argument(); From f911a6d2d9c950d15b29f9bf282c133d35122663 Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Tue, 11 Nov 2025 12:53:13 +0200 Subject: [PATCH 6/9] UI: bug fix calculated fileds --- .../alarm-rules/alarm-rules-table-config.ts | 5 ++++- .../alarm-rules/alarm-rules-table.component.ts | 3 +++ ...le-complex-filter-predicate-dialog.component.html | 1 + ...rule-complex-filter-predicate-dialog.component.ts | 1 + .../filter/alarm-rule-filter-dialog.component.html | 1 + .../alarm-rule-filter-predicate-list.component.html | 1 + .../alarm-rule-filter-predicate-list.component.ts | 3 +++ .../alarm-rule-filter-predicate-value.component.html | 2 +- .../alarm-rule-filter-predicate-value.component.ts | 12 ++++++++---- .../alarm-rule-filter-predicate.component.html | 1 + .../filter/alarm-rule-filter-predicate.component.ts | 4 ++++ .../calculated-fields-table-config.ts | 5 ++++- .../calculated-fields-table.component.ts | 3 +++ 13 files changed, 35 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts index 2b70d951cb..5167e32853 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts @@ -54,6 +54,7 @@ import { CalculatedFieldDebugDialogData } from "@home/components/calculated-fields/components/debug-dialog/calculated-field-debug-dialog.component"; import { AlarmSeverity, alarmSeverityTranslations } from "@shared/models/alarm.models"; +import { UtilsService } from "@core/services/utils.service"; export class AlarmRulesTableConfig extends EntityTableConfig { @@ -75,6 +76,7 @@ export class AlarmRulesTableConfig extends EntityTableConfig { private ownerId: EntityId = null, private importExportService: ImportExportService, private entityDebugSettingsService: EntityDebugSettingsService, + private utilsService: UtilsService, ) { super(); this.tableTitle = this.translate.instant('alarm-rule.alarm-rules'); @@ -115,7 +117,8 @@ export class AlarmRulesTableConfig extends EntityTableConfig { this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; this.columns.push(new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '150px')); - this.columns.push(new EntityTableColumn('name', 'alarm-rule.alarm-type', '33%')); + this.columns.push(new EntityTableColumn('name', 'alarm-rule.alarm-type', '33%', + entity => this.utilsService.customTranslation(entity.name, entity.name))); this.columns.push(new EntityTableColumn('createRule', 'alarm-rule.severities', '67%', entity => Object.keys(entity.configuration.createRules).map((severity) => this.translate.instant(alarmSeverityTranslations.get(severity as AlarmSeverity))).join(', '), () => ({}), false)); diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table.component.ts index 9a553c1ffa..4ade1c5461 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table.component.ts @@ -35,6 +35,7 @@ import { ImportExportService } from '@shared/import-export/import-export.service import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service'; import { DatePipe } from '@angular/common'; import { AlarmRulesTableConfig } from "@home/components/alarm-rules/alarm-rules-table-config"; +import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-alarm-rules-table', @@ -63,6 +64,7 @@ export class AlarmRulesTableComponent { private renderer: Renderer2, private importExportService: ImportExportService, private entityDebugSettingsService: EntityDebugSettingsService, + private utilsService: UtilsService, private destroyRef: DestroyRef) { effect(() => { @@ -80,6 +82,7 @@ export class AlarmRulesTableComponent { this.ownerId(), this.importExportService, this.entityDebugSettingsService, + this.utilsService, ); this.cd.markForCheck(); } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.html index ed5dc4b9cd..75bce37a80 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.html @@ -35,6 +35,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.ts index c8766b0b17..941ce326e1 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.ts @@ -35,6 +35,7 @@ export interface AlarmRuleComplexFilterPredicateDialogData { isAdd: boolean; valueType: EntityKeyValueType; arguments: Record; + argumentInUse: string; } @Component({ diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.html index e622e9e4f8..abc1dd463d 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.html @@ -77,6 +77,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-list.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-list.component.html index 4eaa1eafb6..b7450262f6 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-list.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-list.component.html @@ -42,6 +42,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-list.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-list.component.ts index 646cd720fc..ecb52944b2 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-list.component.ts @@ -78,6 +78,8 @@ export class AlarmRuleFilterPredicateListComponent implements ControlValueAccess @Input() arguments: Record; + @Input() argumentInUse: string; + filterListFormGroup = this.fb.group({ predicates: this.fb.array([]) }); @@ -195,6 +197,7 @@ export class AlarmRuleFilterPredicateListComponent implements ControlValueAccess valueType: this.valueType, isAdd: true, arguments: this.arguments, + argumentInUse: this.argumentInUse } }).afterClosed().pipe( map(result => result) diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.html index c7bcc92012..10294e851a 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.html @@ -55,7 +55,7 @@ @for (argument of argumentsList; track argument) { - {{ argument }} + {{ argument }} } @if (filterPredicateValueFormGroup.get('dynamicValueArgument').touched && filterPredicateValueFormGroup.get('dynamicValueArgument').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.ts index 9675ad5fea..036083c826 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate-value.component.ts @@ -57,6 +57,9 @@ export class AlarmRuleFilterPredicateValueComponent implements ControlValueAcces @Input() valueType: EntityKeyValueType; + @Input() + argumentInUse: string; + valueTypeEnum = EntityKeyValueType; filterPredicateValueFormGroup: FormGroup>>; @@ -104,7 +107,7 @@ export class AlarmRuleFilterPredicateValueComponent implements ControlValueAcces }); this.dynamicModeControl.valueChanges.pipe( takeUntilDestroyed(this.destroyRef) - ).subscribe(value => this.updateValueModeValidators(value)) + ).subscribe(value => this.updateValueModeValidators(value)); } setDisabledState(isDisabled: boolean): void { @@ -114,16 +117,17 @@ export class AlarmRuleFilterPredicateValueComponent implements ControlValueAcces } else { this.filterPredicateValueFormGroup.enable({emitEvent: false}); this.dynamicModeControl.enable({emitEvent: false}); + this.updateValueModeValidators(this.dynamicModeControl.value); } } private updateValueModeValidators(isDynamicMode: boolean): void { if (isDynamicMode) { this.filterPredicateValueFormGroup.get('staticValue').disable({emitEvent: false}); - this.filterPredicateValueFormGroup.get('dynamicValueArgument').enable({emitEvent: false}); + this.filterPredicateValueFormGroup.get('dynamicValueArgument').enable(); } else { - this.filterPredicateValueFormGroup.get('staticValue').enable({emitEvent: false}); this.filterPredicateValueFormGroup.get('dynamicValueArgument').disable({emitEvent: false}); + this.filterPredicateValueFormGroup.get('staticValue').enable(); } } @@ -142,7 +146,7 @@ export class AlarmRuleFilterPredicateValueComponent implements ControlValueAcces writeValue(predicateValue: AlarmRuleValue): void { this.filterPredicateValueFormGroup.patchValue(predicateValue, {emitEvent: false}); - this.dynamicModeControl.patchValue(!!predicateValue.dynamicValueArgument?.length); + this.dynamicModeControl.patchValue(!!predicateValue.dynamicValueArgument?.length, {emitEvent: false}); } private updateModel() { diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.html index 6e440eac86..d73c222f9d 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.html @@ -71,6 +71,7 @@ @if (type !== filterPredicateType.COMPLEX) { diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts index 30c8ec5d2b..b508be0c79 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts @@ -70,6 +70,9 @@ export class AlarmRuleFilterPredicateComponent implements ControlValueAccessor, @Input() arguments: Record; + @Input() + argumentInUse: string; + filterPredicateFormGroup = this.fb.group({ operation: [], ignoreCase: false, @@ -146,6 +149,7 @@ export class AlarmRuleFilterPredicateComponent implements ControlValueAccessor, valueType: this.valueType, isAdd: false, arguments: this.arguments, + argumentInUse: this.argumentInUse, } }).afterClosed().subscribe( (result) => { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts index 8cc5d10fc6..0ac64b428f 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts @@ -59,6 +59,7 @@ import { ImportExportService } from '@shared/import-export/import-export.service import { isObject } from '@core/utils'; import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service'; import { DatePipe } from '@angular/common'; +import { UtilsService } from "@core/services/utils.service"; export class CalculatedFieldsTableConfig extends EntityTableConfig { @@ -80,6 +81,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig('createdTime', 'common.created-time', this.datePipe, '150px')); - this.columns.push(new EntityTableColumn('name', 'common.name', '33%')); + this.columns.push(new EntityTableColumn('name', 'common.name', '33%', + entity => this.utilsService.customTranslation(entity.name, entity.name))); this.columns.push(new EntityTableColumn('type', 'common.type', '170px', entity => this.translate.instant(CalculatedFieldTypeTranslations.get(entity.type).name), () => ({whiteSpace: 'nowrap' }))); this.columns.push(expressionColumn); diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts index ac7fea9454..ee3d013907 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts @@ -35,6 +35,7 @@ import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { ImportExportService } from '@shared/import-export/import-export.service'; import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service'; import { DatePipe } from '@angular/common'; +import { UtilsService } from "@core/services/utils.service"; @Component({ selector: 'tb-calculated-fields-table', @@ -63,6 +64,7 @@ export class CalculatedFieldsTableComponent { private renderer: Renderer2, private importExportService: ImportExportService, private entityDebugSettingsService: EntityDebugSettingsService, + private utilsService: UtilsService, private destroyRef: DestroyRef) { effect(() => { @@ -80,6 +82,7 @@ export class CalculatedFieldsTableComponent { this.ownerId(), this.importExportService, this.entityDebugSettingsService, + this.utilsService, ); this.cd.markForCheck(); } From e1ddc5fd0ef484a860d99b5b53ae94a8d7138a98 Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Tue, 11 Nov 2025 13:18:28 +0200 Subject: [PATCH 7/9] UI: Custom schedule align to center --- .../components/alarm-rules/cf-alarm-schedule.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-schedule.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-schedule.component.html index 13c158e67d..7fe09d7850 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-schedule.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-schedule.component.html @@ -90,7 +90,7 @@ -
From a229e450c316177de32d52873246d4f3bb114d42 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 12 Nov 2025 10:56:32 +0200 Subject: [PATCH 8/9] Fix notification template files --- .../help/en_US/notification/edge_communication_failure.md | 3 +++ ui-ngx/src/assets/help/en_US/notification/edge_connection.md | 3 +++ .../src/assets/help/en_US/notification/resources_shortage.md | 3 +++ .../assets/help/en_US/notification/task_processing_failure.md | 3 +++ 4 files changed, 12 insertions(+) diff --git a/ui-ngx/src/assets/help/en_US/notification/edge_communication_failure.md b/ui-ngx/src/assets/help/en_US/notification/edge_communication_failure.md index 712f2b45e7..0df4732ab4 100644 --- a/ui-ngx/src/assets/help/en_US/notification/edge_communication_failure.md +++ b/ui-ngx/src/assets/help/en_US/notification/edge_communication_failure.md @@ -12,6 +12,9 @@ Available template parameters: * `edgeId` - the edge id as uuid string; * `edgeName` - the name of the edge; * `failureMsg` - the string representation of the failure, occurred on the Edge; +* `recipientEmail` - email of the recipient; +* `recipientFirstName` - first name of the recipient; +* `recipientLastName` - last name of the recipient; Parameter names must be wrapped using `${...}`. For example: `${edgeName}`. You may also modify the value of the parameter with one of the suffixes: diff --git a/ui-ngx/src/assets/help/en_US/notification/edge_connection.md b/ui-ngx/src/assets/help/en_US/notification/edge_connection.md index 37f0ec7573..4c97f6ccde 100644 --- a/ui-ngx/src/assets/help/en_US/notification/edge_connection.md +++ b/ui-ngx/src/assets/help/en_US/notification/edge_connection.md @@ -12,6 +12,9 @@ Available template parameters: * `edgeId` - the edge id as uuid string; * `edgeName` - the name of the edge; * `eventType` - the string representation of the connectivity status: connected or disconnected; +* `recipientEmail` - email of the recipient; +* `recipientFirstName` - first name of the recipient; +* `recipientLastName` - last name of the recipient; Parameter names must be wrapped using `${...}`. For example: `${edgeName}`. You may also modify the value of the parameter with one of the suffixes: diff --git a/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md b/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md index 6ea03a514c..2c0634a5f0 100644 --- a/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md +++ b/ui-ngx/src/assets/help/en_US/notification/resources_shortage.md @@ -13,6 +13,9 @@ Available template parameters: * `usage` - the current usage value of the resource; * `serviceId` - the service id (convenient in cluster setup); * `serviceType` - the service type (convenient in cluster setup); +* `recipientEmail` - email of the recipient; +* `recipientFirstName` - first name of the recipient; +* `recipientLastName` - last name of the recipient; Parameter names must be wrapped using `${...}`. For example: `${resource}`. You may also modify the value of the parameter with one of the suffixes: diff --git a/ui-ngx/src/assets/help/en_US/notification/task_processing_failure.md b/ui-ngx/src/assets/help/en_US/notification/task_processing_failure.md index 0bcf107298..da46dc11bd 100644 --- a/ui-ngx/src/assets/help/en_US/notification/task_processing_failure.md +++ b/ui-ngx/src/assets/help/en_US/notification/task_processing_failure.md @@ -16,6 +16,9 @@ Available template parameters: * `entityType` - the type of the entity to which the task is related; * `entityId` - the id of the entity to which the task is related; * `attempt` - the number of attempts processing the task +* `recipientEmail` - email of the recipient; +* `recipientFirstName` - first name of the recipient; +* `recipientLastName` - last name of the recipient; Parameter names must be wrapped using `${...}`. For example: `${entityType}`. You may also modify the value of the parameter with one of the suffixes: From 31aaf6e8e21e4a3cd7d743a36f4b45b1636be942 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 12 Nov 2025 11:35:35 +0200 Subject: [PATCH 9/9] Fix potential issue with details init --- .../common/data/notification/info/AlarmNotificationInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java index 00040db7aa..5ad33b1e6f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java @@ -51,7 +51,7 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo { @Override public Map getTemplateData() { - Map templateData = new HashMap<>(details); + Map templateData = details != null ? new HashMap<>(details) : new HashMap<>(); templateData.put("alarmType", alarmType); templateData.put("action", action); templateData.put("alarmId", alarmId.toString());