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.HasName;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; 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.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.iot_hub.SolutionTemplateInstalledItemDescriptor; 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.Device;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.cf.CalculatedField; 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.debug.DebugSettings;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.AlarmId; 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.user.UserService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.exception.EntitiesLimitExceededException; 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.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; 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."); throw new RuntimeException("Calculated field: " + cf.getName() + " references non existing entity.");
} }
} }
if (cf.getConfiguration() instanceof BaseCalculatedFieldConfiguration baseCfg) { if (cf.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration argBasedCfg) {
baseCfg.getArguments().forEach((key, argument) -> { argBasedCfg.getArguments().forEach((key, argument) -> {
EntityId refEntityId = argument.getRefEntityId(); EntityId refEntityId = argument.getRefEntityId();
if (refEntityId != null) { if (refEntityId != null) {
if (refEntityId.getEntityType() == EntityType.TENANT) { if (refEntityId.getEntityType() == EntityType.TENANT) {
@ -1340,8 +1341,8 @@ public class DefaultSolutionService implements SolutionService {
if (newId != null) { if (newId != null) {
argument.setRefEntityId(EntityIdFactory.getByTypeAndUuid(refEntityId.getEntityType(), newId)); argument.setRefEntityId(EntityIdFactory.getByTypeAndUuid(refEntityId.getEntityType(), newId));
} else { } else {
log.error("[{}] Calculated field: {} references non existing entity.", ctx.getTenantId(), cf.getName()); log.error("[{}][{}] Calculated field: {} references non existing entity.", ctx.getTenantId(), ctx.getSolutionId(), cf.getName());
throw new RuntimeException("Calculated field: " + cf.getName() + " references non existing entity."); 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; 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 org.thingsboard.server.common.data.id.EntityId;
import java.util.UUID; 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) { public static CreatedAlarmRuleInfo from(EntityId entityId, String entityName, CalculatedField calculatedField) {
return "/alarms/alarm-rules/" + cfId; if (calculatedField.getType() != CalculatedFieldType.ALARM) {
} throw new UnsupportedOperationException("Only alarm calculated fields are supported");
public String getEntityPageLink() {
if (entityId == null) {
return null;
} }
return switch (entityId.getEntityType()) { String severities = ((AlarmCalculatedFieldConfiguration) calculatedField.getConfiguration())
case DEVICE_PROFILE -> "/profiles/deviceProfiles/" + entityId.getId(); .getCreateRules().keySet().stream()
case ASSET_PROFILE -> "/profiles/assetProfiles/" + entityId.getId(); .map(AlarmSeverity::getDisplayName)
default -> null; .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; package org.thingsboard.server.service.solutions.data;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import java.util.UUID; 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) { public static CreatedCalculatedFieldInfo from(EntityId entityId, String entityName, CalculatedField calculatedField) {
return "/calculatedFields/" + cfId; return new CreatedCalculatedFieldInfo(entityId, entityName, calculatedField.getType().getDisplayName(), calculatedField.getName());
} }
public String getEntityPageLink() { @Override
if (entityId == null) { public String getCfPageLink(UUID cfId) {
return null; return "/calculatedFields/" + cfId;
}
return switch (entityId.getEntityType()) {
case DEVICE_PROFILE -> "/profiles/deviceProfiles/" + entityId.getId();
case ASSET_PROFILE -> "/profiles/assetProfiles/" + entityId.getId();
default -> null;
};
} }
} }

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.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.cf.CalculatedField; 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.edge.Edge;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -156,17 +157,17 @@ public class SolutionInstallContext {
register(calculatedField.getId()); register(calculatedField.getId());
EntityId entityId = calculatedField.getEntityId(); EntityId entityId = calculatedField.getEntityId();
CreatedEntityInfo entityInfo = createdEntities.get(entityId.getId()); 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) { if (entityInfo == null) {
String target = alarmRule ? "Alarm rule" : "Calculated field"; String target = alarmRule ? "Alarm rule" : "Calculated field";
throw new IllegalStateException("Failed to register " + target + " with name: " + throw new IllegalStateException("Failed to register " + target + " with name: " +
calculatedField.getName() + " for non-existing entity with id: " + entityId); calculatedField.getName() + " for non-existing entity with id: " + entityId);
} }
if (alarmRule) { 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; 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) { 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; package org.thingsboard.server.common.data.alarm;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
public enum AlarmSeverity { public enum AlarmSeverity {
CRITICAL, MAJOR, MINOR, WARNING, INDETERMINATE; 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; package org.thingsboard.server.common.data.cf;
import lombok.Getter;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Set; import java.util.Set;
public enum CalculatedFieldType { public enum CalculatedFieldType {
SIMPLE, SIMPLE("Simple"),
SCRIPT, SCRIPT("Script"),
GEOFENCING, GEOFENCING("Geofencing"),
ALARM, ALARM("Alarm"),
PROPAGATION, PROPAGATION("Propagation"),
RELATED_ENTITIES_AGGREGATION, RELATED_ENTITIES_AGGREGATION("Related entities aggregation"),
ENTITY_AGGREGATION; ENTITY_AGGREGATION("Time series data aggregation");
public static final Set<CalculatedFieldType> all = Collections.unmodifiableSet(EnumSet.allOf(CalculatedFieldType.class)); 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