Browse Source

feat(solutions): wire ALARM calculated field info and add display names

- Introduce HasAppliedToEntity interface used by CreatedAlarmRuleInfo and
  CreatedCalculatedFieldInfo to share the entity page-link logic and
  cover DEVICE / ASSET in addition to the profile types.
- Add static from(EntityId, name, CalculatedField) factories to both
  records; SolutionInstallContext now uses them and detects ALARM
  calculated fields via type instead of the previous TODO/hardcoded
  false.
- AlarmSeverity and CalculatedFieldType expose display names used when
  formatting created-alarm-rule severities and CF type column.
- DefaultSolutionService switches the CF arguments check from
  BaseCalculatedFieldConfiguration to ArgumentsBasedCalculatedFieldConfiguration
  and throws ThingsboardRuntimeException for missing references.
pull/15548/head
Igor Kulikov 1 month ago
parent
commit
a4fe68fdb3
  1. 11
      application/src/main/java/org/thingsboard/server/service/solutions/DefaultSolutionService.java
  2. 33
      application/src/main/java/org/thingsboard/server/service/solutions/data/CreatedAlarmRuleInfo.java
  3. 21
      application/src/main/java/org/thingsboard/server/service/solutions/data/CreatedCalculatedFieldInfo.java
  4. 43
      application/src/main/java/org/thingsboard/server/service/solutions/data/HasAppliedToEntity.java
  5. 7
      application/src/main/java/org/thingsboard/server/service/solutions/data/SolutionInstallContext.java
  6. 6
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSeverity.java
  7. 23
      common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldType.java

11
application/src/main/java/org/thingsboard/server/service/solutions/DefaultSolutionService.java

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.iot_hub.SolutionTemplateInstalledItemDescriptor;
@ -55,7 +56,6 @@ import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.configuration.BaseCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.debug.DebugSettings;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.AlarmId;
@ -94,6 +94,7 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.exception.EntitiesLimitExceededException;
import org.thingsboard.server.exception.ThingsboardRuntimeException;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
@ -1329,8 +1330,8 @@ public class DefaultSolutionService implements SolutionService {
throw new RuntimeException("Calculated field: " + cf.getName() + " references non existing entity.");
}
}
if (cf.getConfiguration() instanceof BaseCalculatedFieldConfiguration baseCfg) {
baseCfg.getArguments().forEach((key, argument) -> {
if (cf.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration argBasedCfg) {
argBasedCfg.getArguments().forEach((key, argument) -> {
EntityId refEntityId = argument.getRefEntityId();
if (refEntityId != null) {
if (refEntityId.getEntityType() == EntityType.TENANT) {
@ -1340,8 +1341,8 @@ public class DefaultSolutionService implements SolutionService {
if (newId != null) {
argument.setRefEntityId(EntityIdFactory.getByTypeAndUuid(refEntityId.getEntityType(), newId));
} else {
log.error("[{}] Calculated field: {} references non existing entity.", ctx.getTenantId(), cf.getName());
throw new RuntimeException("Calculated field: " + cf.getName() + " references non existing entity.");
log.error("[{}][{}] Calculated field: {} references non existing entity.", ctx.getTenantId(), ctx.getSolutionId(), cf.getName());
throw new ThingsboardRuntimeException();
}
}
}

33
application/src/main/java/org/thingsboard/server/service/solutions/data/CreatedAlarmRuleInfo.java

@ -15,25 +15,32 @@
*/
package org.thingsboard.server.service.solutions.data;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.AlarmCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.UUID;
import java.util.stream.Collectors;
public record CreatedAlarmRuleInfo(EntityId entityId, String entityName, String alarmType, String severities) {
public record CreatedAlarmRuleInfo(EntityId entityId, String entityName, String alarmType,
String severities) implements HasAppliedToEntity {
public String getCfPageLink(UUID cfId) {
return "/alarms/alarm-rules/" + cfId;
}
public String getEntityPageLink() {
if (entityId == null) {
return null;
public static CreatedAlarmRuleInfo from(EntityId entityId, String entityName, CalculatedField calculatedField) {
if (calculatedField.getType() != CalculatedFieldType.ALARM) {
throw new UnsupportedOperationException("Only alarm calculated fields are supported");
}
return switch (entityId.getEntityType()) {
case DEVICE_PROFILE -> "/profiles/deviceProfiles/" + entityId.getId();
case ASSET_PROFILE -> "/profiles/assetProfiles/" + entityId.getId();
default -> null;
};
String severities = ((AlarmCalculatedFieldConfiguration) calculatedField.getConfiguration())
.getCreateRules().keySet().stream()
.map(AlarmSeverity::getDisplayName)
.sorted()
.collect(Collectors.joining(", "));
return new CreatedAlarmRuleInfo(entityId, entityName, calculatedField.getName(), severities);
}
@Override
public String getCfPageLink(UUID cfId) {
return "/alarms/alarm-rules/" + cfId;
}
}

21
application/src/main/java/org/thingsboard/server/service/solutions/data/CreatedCalculatedFieldInfo.java

@ -15,25 +15,20 @@
*/
package org.thingsboard.server.service.solutions.data;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.UUID;
public record CreatedCalculatedFieldInfo(EntityId entityId, String entityName, String type, String name) {
public record CreatedCalculatedFieldInfo(EntityId entityId, String entityName, String type,
String name) implements HasAppliedToEntity {
public String getCfPageLink(UUID cfId) {
return "/calculatedFields/" + cfId;
public static CreatedCalculatedFieldInfo from(EntityId entityId, String entityName, CalculatedField calculatedField) {
return new CreatedCalculatedFieldInfo(entityId, entityName, calculatedField.getType().getDisplayName(), calculatedField.getName());
}
public String getEntityPageLink() {
if (entityId == null) {
return null;
}
return switch (entityId.getEntityType()) {
case DEVICE_PROFILE -> "/profiles/deviceProfiles/" + entityId.getId();
case ASSET_PROFILE -> "/profiles/assetProfiles/" + entityId.getId();
default -> null;
};
@Override
public String getCfPageLink(UUID cfId) {
return "/calculatedFields/" + cfId;
}
}

43
application/src/main/java/org/thingsboard/server/service/solutions/data/HasAppliedToEntity.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2026 The Thingsboard Authors
*
* Licensed 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.thingsboard.server.service.solutions.data;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.UUID;
public interface HasAppliedToEntity {
EntityId entityId();
String getCfPageLink(UUID cfId);
default String getEntityPageLink() {
EntityId id = entityId();
if (id == null) {
return null;
}
String idStr = id.getId().toString();
return switch (id.getEntityType()) {
case DEVICE_PROFILE -> "/profiles/deviceProfiles/" + idStr;
case ASSET_PROFILE -> "/profiles/assetProfiles/" + idStr;
case DEVICE -> "/entities/devices/" + idStr;
case ASSET -> "/entities/assets/" + idStr;
default -> null;
};
}
}

7
application/src/main/java/org/thingsboard/server/service/solutions/data/SolutionInstallContext.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -156,17 +157,17 @@ public class SolutionInstallContext {
register(calculatedField.getId());
EntityId entityId = calculatedField.getEntityId();
CreatedEntityInfo entityInfo = createdEntities.get(entityId.getId());
boolean alarmRule = false; // TODO: CE doesn't have ALARM calculated field type yet
boolean alarmRule = calculatedField.getType() == CalculatedFieldType.ALARM;
if (entityInfo == null) {
String target = alarmRule ? "Alarm rule" : "Calculated field";
throw new IllegalStateException("Failed to register " + target + " with name: " +
calculatedField.getName() + " for non-existing entity with id: " + entityId);
}
if (alarmRule) {
createdAlarmRules.put(calculatedField.getUuidId(), new CreatedAlarmRuleInfo(entityId, entityInfo.getName(), calculatedField.getName(), null));
createdAlarmRules.put(calculatedField.getUuidId(), CreatedAlarmRuleInfo.from(entityId, entityInfo.getName(), calculatedField));
return;
}
createdCalculatedFields.put(calculatedField.getUuidId(), new CreatedCalculatedFieldInfo(entityId, entityInfo.getName(), calculatedField.getType().name(), calculatedField.getName()));
createdCalculatedFields.put(calculatedField.getUuidId(), CreatedCalculatedFieldInfo.from(entityId, entityInfo.getName(), calculatedField));
}
public void putIdToMap(EntityDefinition entityDefinition, EntityId entityId) {

6
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSeverity.java

@ -15,8 +15,14 @@
*/
package org.thingsboard.server.common.data.alarm;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
public enum AlarmSeverity {
CRITICAL, MAJOR, MINOR, WARNING, INDETERMINATE;
@Getter
private final String displayName = StringUtils.capitalize(name().toLowerCase());
}

23
common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldType.java

@ -15,20 +15,29 @@
*/
package org.thingsboard.server.common.data.cf;
import lombok.Getter;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
public enum CalculatedFieldType {
SIMPLE,
SCRIPT,
GEOFENCING,
ALARM,
PROPAGATION,
RELATED_ENTITIES_AGGREGATION,
ENTITY_AGGREGATION;
SIMPLE("Simple"),
SCRIPT("Script"),
GEOFENCING("Geofencing"),
ALARM("Alarm"),
PROPAGATION("Propagation"),
RELATED_ENTITIES_AGGREGATION("Related entities aggregation"),
ENTITY_AGGREGATION("Time series data aggregation");
public static final Set<CalculatedFieldType> all = Collections.unmodifiableSet(EnumSet.allOf(CalculatedFieldType.class));
@Getter
private final String displayName;
CalculatedFieldType(String displayName) {
this.displayName = displayName;
}
}

Loading…
Cancel
Save