Browse Source

Merge branch 'feature/entity-alarm-rules' of github.com:thingsboard/thingsboard into feature/aggregation-cf

pull/14141/head
IrynaMatveieva 11 months ago
parent
commit
9ae9dbcda2
  1. 125
      application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
  2. 13
      application/src/main/data/json/system/widget_types/alarms_table.json
  3. 13
      application/src/main/data/json/system/widget_types/entities_table.json
  4. 44
      application/src/main/data/json/tenant/device_profile/rule_chain_template.json
  5. 20
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  6. 2
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  7. 279
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  8. 1
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  9. 1
      application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java
  10. 160
      application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
  11. 5
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java
  12. 4
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/SimpleAlarmConditionExpression.java
  13. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java
  14. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java
  15. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java
  16. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionKeyType.java
  17. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionSpec.java
  18. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionSpecType.java
  19. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java
  20. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java
  21. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmScheduleType.java
  22. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java
  23. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java
  24. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeScheduleItem.java
  25. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java
  26. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java
  27. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java
  28. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SimpleAlarmConditionSpec.java
  29. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java
  30. 2
      common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java
  31. 22
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/BaseEntityData.java
  32. 1
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/DeviceData.java
  33. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java
  34. 27
      dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java
  35. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java
  36. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java
  37. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
  38. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DataSnapshot.java
  39. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java
  40. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtx.java
  41. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtxImpl.java
  42. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/EntityKeyValue.java
  43. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java
  44. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/SnapshotUpdate.java
  45. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java
  46. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeConfiguration.java
  47. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/state/PersistedAlarmRuleState.java
  48. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/state/PersistedAlarmState.java
  49. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/state/PersistedDeviceState.java
  50. 2
      ui-ngx/src/app/core/http/entity.service.ts
  51. 2
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html
  52. 8
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts
  53. 1
      ui-ngx/src/app/shared/components/entity/entity-list-select.component.html
  54. 3
      ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts
  55. 4
      ui-ngx/src/app/shared/components/entity/entity-list.component.html
  56. 8
      ui-ngx/src/app/shared/components/entity/entity-list.component.ts
  57. 1
      ui-ngx/src/app/shared/components/entity/entity-select.component.html
  58. 4
      ui-ngx/src/app/shared/components/entity/entity-select.component.ts
  59. 7
      ui-ngx/src/app/shared/models/alarm.models.ts
  60. 13
      ui-ngx/src/app/shared/models/base-data.ts
  61. 5
      ui-ngx/src/app/shared/models/entity.models.ts

125
application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json

@ -10,27 +10,9 @@
"externalId": null
},
"metadata": {
"firstNodeIndex": 0,
"firstNodeIndex": 2,
"nodes": [
{
"additionalInfo": {
"description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
"layoutX": 187,
"layoutY": 468
},
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node",
"configuration": {
"persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false
},
"externalId": null
},
{
"additionalInfo": {
"layoutX": 823,
"layoutY": 157
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries",
"configurationVersion": 1,
@ -41,13 +23,12 @@
"type": "ON_EVERY_MESSAGE"
}
},
"externalId": null
"additionalInfo": {
"layoutX": 823,
"layoutY": 157
}
},
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 52
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
"configurationVersion": 3,
@ -60,25 +41,23 @@
"sendAttributesUpdatedNotification": false,
"updateAttributesOnlyOnValueChange": true
},
"externalId": null
"additionalInfo": {
"layoutX": 824,
"layoutY": 52
}
},
{
"additionalInfo": {
"layoutX": 347,
"layoutY": 149
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch",
"configuration": {
"version": 0
},
"externalId": null
"additionalInfo": {
"layoutX": 347,
"layoutY": 149
}
},
{
"additionalInfo": {
"layoutX": 825,
"layoutY": 266
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device",
"configuration": {
@ -86,13 +65,12 @@
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
},
"externalId": null
"additionalInfo": {
"layoutX": 825,
"layoutY": 266
}
},
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 378
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other",
"configuration": {
@ -100,97 +78,92 @@
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
},
"externalId": null
},
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 466
},
"layoutY": 378
}
},
{
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request",
"configuration": {
"timeoutInSeconds": 60
},
"externalId": null
"additionalInfo": {
"layoutX": 824,
"layoutY": 466
}
},
{
"additionalInfo": {
"layoutX": 1126,
"layoutY": 104
},
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud",
"configuration": {
"scope": "CLIENT_SCOPE"
},
"externalId": null
"additionalInfo": {
"layoutX": 1126,
"layoutY": 104
}
},
{
"additionalInfo": {
"layoutX": 826,
"layoutY": 601
},
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud",
"configuration": {
"scope": "SERVER_SCOPE"
},
"externalId": null
"additionalInfo": {
"layoutX": 826,
"layoutY": 601
}
}
],
"connections": [
{
"fromIndex": 0,
"toIndex": 3,
"toIndex": 6,
"type": "Success"
},
{
"fromIndex": 1,
"toIndex": 7,
"toIndex": 6,
"type": "Success"
},
{
"fromIndex": 2,
"toIndex": 7,
"type": "Success"
},
{
"fromIndex": 3,
"toIndex": 1,
"toIndex": 0,
"type": "Post telemetry"
},
{
"fromIndex": 3,
"toIndex": 2,
"fromIndex": 2,
"toIndex": 1,
"type": "Post attributes"
},
{
"fromIndex": 3,
"toIndex": 4,
"fromIndex": 2,
"toIndex": 3,
"type": "RPC Request from Device"
},
{
"fromIndex": 3,
"toIndex": 5,
"fromIndex": 2,
"toIndex": 4,
"type": "Other"
},
{
"fromIndex": 3,
"toIndex": 6,
"fromIndex": 2,
"toIndex": 5,
"type": "RPC Request to Device"
},
{
"fromIndex": 3,
"toIndex": 8,
"fromIndex": 2,
"toIndex": 7,
"type": "Attributes Deleted"
},
{
"fromIndex": 3,
"toIndex": 8,
"fromIndex": 2,
"toIndex": 7,
"type": "Attributes Updated"
}
],
"ruleChainConnections": null
}
}
}

13
application/src/main/data/json/system/widget_types/alarms_table.json

File diff suppressed because one or more lines are too long

13
application/src/main/data/json/system/widget_types/entities_table.json

@ -11,19 +11,13 @@
"resources": [],
"templateHtml": "<tb-entities-table-widget \n [ctx]=\"ctx\">\n</tb-entities-table-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.entitiesTableWidget.onEditModeChanged();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true,\n supportsUnitConversion: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'name', type: 'entityField' }];\n }\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n },\n 'cellClick': {\n name: 'widget-action.cell-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.entitiesTableWidget.onEditModeChanged();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true,\n supportsUnitConversion: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'displayName', type: 'entityField' }];\n }\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n },\n 'cellClick': {\n name: 'widget-action.cell-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsDirective": "tb-entities-table-widget-settings",
"dataKeySettingsDirective": "tb-entities-table-key-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-entities-table-basic-config",
"defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"reserveSpaceForHiddenAction\":\"true\",\"displayEntityName\":false,\"displayEntityLabel\":false,\"displayEntityType\":false,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"name\",\"useRowStyleFunction\":false,\"entitiesTitle\":\"Entities\"},\"title\":\"Entities table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity name\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return 'Simulated';\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity type\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.782057645776538,\"funcBody\":\"return 'Device';\",\"decimals\":null,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.904797781901171,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\",\"decimals\":0},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.1961430898042078,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\",\"decimals\":0},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.7678057538205878,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"decimals\":2}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"displayTimewindow\":false,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"list\",\"iconColor\":null}"
},
"tags": [
"administration",
"management"
],
"resources": [
{
"link": "/api/images/system/entities_table_system_widget_image.png",
@ -36,5 +30,10 @@
"data": "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",
"public": true
}
],
"scada": false,
"tags": [
"administration",
"management"
]
}

44
application/src/main/data/json/tenant/device_profile/rule_chain_template.json

@ -10,12 +10,12 @@
"configuration": null
},
"metadata": {
"firstNodeIndex": 6,
"firstNodeIndex": 2,
"nodes": [
{
"additionalInfo": {
"layoutX": 822,
"layoutY": 294
"layoutX": 824,
"layoutY": 156
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries",
@ -30,8 +30,8 @@
},
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 221
"layoutX": 825,
"layoutY": 52
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
@ -48,8 +48,8 @@
},
{
"additionalInfo": {
"layoutX": 494,
"layoutY": 309
"layoutX": 347,
"layoutY": 149
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch",
@ -59,8 +59,8 @@
},
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 383
"layoutX": 825,
"layoutY": 266
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device",
@ -72,8 +72,8 @@
},
{
"additionalInfo": {
"layoutX": 823,
"layoutY": 444
"layoutX": 825,
"layoutY": 379
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other",
@ -85,27 +85,14 @@
},
{
"additionalInfo": {
"layoutX": 822,
"layoutY": 507
"layoutX": 825,
"layoutY": 468
},
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request",
"configuration": {
"timeoutInSeconds": 60
}
},
{
"additionalInfo": {
"description": "",
"layoutX": 209,
"layoutY": 307
},
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node",
"configuration": {
"persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false
}
}
],
"connections": [
@ -133,11 +120,6 @@
"fromIndex": 2,
"toIndex": 5,
"type": "RPC Request to Device"
},
{
"fromIndex": 6,
"toIndex": 2,
"type": "Success"
}
],
"ruleChainConnections": null

20
application/src/main/data/json/tenant/rule_chains/root_rule_chain.json

@ -9,7 +9,7 @@
"configuration": null
},
"metadata": {
"firstNodeIndex": 6,
"firstNodeIndex": 2,
"nodes": [
{
"additionalInfo": {
@ -92,27 +92,9 @@
"configuration": {
"timeoutInSeconds": 60
}
},
{
"additionalInfo": {
"description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
"layoutX": 204,
"layoutY": 240
},
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node",
"configuration": {
"persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false
}
}
],
"connections": [
{
"fromIndex": 6,
"toIndex": 2,
"type": "Success"
},
{
"fromIndex": 2,
"toIndex": 4,

2
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -165,7 +165,7 @@ public class AppActor extends ContextAwareActor {
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
TbActorRef target = null;
if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) {
if (!EntityType.TENANT_PROFILE.equals(msg.getEntityId().getEntityType())) {
if (!msg.getEntityId().getEntityType().isOneOf(EntityType.TENANT_PROFILE, EntityType.TB_RESOURCE)) {
log.warn("Message has system tenant id: {}", msg);
}
} else {

279
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -49,17 +49,25 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.alarm.rule.AlarmRule;
import org.thingsboard.server.common.data.alarm.rule.condition.AlarmConditionValue;
import org.thingsboard.server.common.data.alarm.rule.condition.SimpleAlarmCondition;
import org.thingsboard.server.common.data.alarm.rule.condition.expression.AlarmConditionFilter;
import org.thingsboard.server.common.data.alarm.rule.condition.expression.ComplexOperation;
import org.thingsboard.server.common.data.alarm.rule.condition.expression.SimpleAlarmConditionExpression;
import org.thingsboard.server.common.data.alarm.rule.condition.expression.predicate.BooleanFilterPredicate;
import org.thingsboard.server.common.data.alarm.rule.condition.expression.predicate.NumericFilterPredicate;
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.cf.configuration.Argument;
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
import org.thingsboard.server.common.data.debug.DebugSettings;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -74,12 +82,6 @@ import org.thingsboard.server.common.data.kv.TimeseriesSaveResult;
import org.thingsboard.server.common.data.mobile.app.MobileApp;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.queue.ProcessingStrategy;
import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
import org.thingsboard.server.common.data.queue.Queue;
@ -94,6 +96,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceConnectivityConfiguration;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
@ -117,7 +120,7 @@ import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@ -155,6 +158,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
private final MobileAppDao mobileAppDao;
private final NotificationSettingsService notificationSettingsService;
private final NotificationTargetService notificationTargetService;
private final CalculatedFieldService calculatedFieldService;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@ -306,8 +310,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
if (invalidSignKey) {
log.warn("WARNING: {}. A new JWT Signing Key has been added automatically. " +
"You can change the JWT Signing Key using the Web UI: " +
"Navigate to \"System settings -> Security settings\" while logged in as a System Administrator.", warningMessage);
"You can change the JWT Signing Key using the Web UI: " +
"Navigate to \"System settings -> Security settings\" while logged in as a System Administrator.", warningMessage);
jwtSettings.setTokenSigningKey(generateRandomKey());
jwtSettingsService.saveJwtSettings(jwtSettings);
@ -319,9 +323,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
.filter(mobileApp -> !validateKeyLength(mobileApp.getAppSecret()))
.forEach(mobileApp -> {
log.warn("WARNING: The App secret is shorter than 512 bits, which is a security risk. " +
"A new Application Secret has been added automatically for Mobile Application [{}]. " +
"You can change the Application Secret using the Web UI: " +
"Navigate to \"Security settings -> OAuth2 -> Mobile applications\" while logged in as a System Administrator.", mobileApp.getPkgName());
"A new Application Secret has been added automatically for Mobile Application [{}]. " +
"You can change the Application Secret using the Web UI: " +
"Navigate to \"Security settings -> OAuth2 -> Mobile applications\" while logged in as a System Administrator.", mobileApp.getPkgName());
mobileApp.setAppSecret(generateRandomKey());
mobileAppDao.save(TenantId.SYS_TENANT_ID, mobileApp);
});
@ -372,11 +376,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
createDevice(demoTenant.getId(), customerB.getId(), defaultDeviceProfile.getId(), "Test Device B1", "B1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerC.getId(), defaultDeviceProfile.getId(), "Test Device C1", "C1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "DHT11 Demo Device", "DHT11_DEMO_TOKEN", "Demo device that is used in sample " +
"applications that upload data from DHT11 temperature and humidity sensor");
createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "DHT11 Demo Device", "DHT11_DEMO_TOKEN",
"Demo device that is used in sample applications that upload data from DHT11 temperature and humidity sensor");
createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
"Raspberry Pi GPIO control sample application");
createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN",
"Demo device that is used in Raspberry Pi GPIO control sample application");
DeviceProfile thermostatDeviceProfile = new DeviceProfile();
thermostatDeviceProfile.setTenantId(demoTenant.getId());
@ -398,110 +402,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
deviceProfileData.setProvisionConfiguration(provisionConfiguration);
thermostatDeviceProfile.setProfileData(deviceProfileData);
DeviceProfileAlarm highTemperature = new DeviceProfileAlarm();
highTemperature.setId("highTemperatureAlarmID");
highTemperature.setAlarmType("High Temperature");
AlarmRule temperatureRule = new AlarmRule();
AlarmCondition temperatureCondition = new AlarmCondition();
temperatureCondition.setSpec(new SimpleAlarmConditionSpec());
AlarmConditionFilter temperatureAlarmFlagAttributeFilter = new AlarmConditionFilter();
temperatureAlarmFlagAttributeFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "temperatureAlarmFlag"));
temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate();
temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
temperatureAlarmFlagAttributeFilter.setPredicate(temperatureAlarmFlagAttributePredicate);
AlarmConditionFilter temperatureTimeseriesFilter = new AlarmConditionFilter();
temperatureTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
FilterPredicateValue<Double> temperatureTimeseriesPredicateValue =
new FilterPredicateValue<>(25.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold"));
temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue);
temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate);
temperatureCondition.setCondition(Arrays.asList(temperatureAlarmFlagAttributeFilter, temperatureTimeseriesFilter));
temperatureRule.setAlarmDetails("Current temperature = ${temperature}");
temperatureRule.setCondition(temperatureCondition);
highTemperature.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule)));
AlarmRule clearTemperatureRule = new AlarmRule();
AlarmCondition clearTemperatureCondition = new AlarmCondition();
clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec());
AlarmConditionFilter clearTemperatureTimeseriesFilter = new AlarmConditionFilter();
clearTemperatureTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL);
FilterPredicateValue<Double> clearTemperatureTimeseriesPredicateValue =
new FilterPredicateValue<>(25.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold"));
clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue);
clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate);
clearTemperatureCondition.setCondition(Collections.singletonList(clearTemperatureTimeseriesFilter));
clearTemperatureRule.setCondition(clearTemperatureCondition);
clearTemperatureRule.setAlarmDetails("Current temperature = ${temperature}");
highTemperature.setClearRule(clearTemperatureRule);
DeviceProfileAlarm lowHumidity = new DeviceProfileAlarm();
lowHumidity.setId("lowHumidityAlarmID");
lowHumidity.setAlarmType("Low Humidity");
AlarmRule humidityRule = new AlarmRule();
AlarmCondition humidityCondition = new AlarmCondition();
humidityCondition.setSpec(new SimpleAlarmConditionSpec());
AlarmConditionFilter humidityAlarmFlagAttributeFilter = new AlarmConditionFilter();
humidityAlarmFlagAttributeFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "humidityAlarmFlag"));
humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate();
humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagAttributePredicate);
AlarmConditionFilter humidityTimeseriesFilter = new AlarmConditionFilter();
humidityTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "humidity"));
humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate();
humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
FilterPredicateValue<Double> humidityTimeseriesPredicateValue =
new FilterPredicateValue<>(60.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold"));
humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue);
humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate);
humidityCondition.setCondition(Arrays.asList(humidityAlarmFlagAttributeFilter, humidityTimeseriesFilter));
humidityRule.setCondition(humidityCondition);
humidityRule.setAlarmDetails("Current humidity = ${humidity}");
lowHumidity.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule)));
AlarmRule clearHumidityRule = new AlarmRule();
AlarmCondition clearHumidityCondition = new AlarmCondition();
clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec());
AlarmConditionFilter clearHumidityTimeseriesFilter = new AlarmConditionFilter();
clearHumidityTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "humidity"));
clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate();
clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL);
FilterPredicateValue<Double> clearHumidityTimeseriesPredicateValue =
new FilterPredicateValue<>(60.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold"));
clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue);
clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate);
clearHumidityCondition.setCondition(Collections.singletonList(clearHumidityTimeseriesFilter));
clearHumidityRule.setCondition(clearHumidityCondition);
clearHumidityRule.setAlarmDetails("Current humidity = ${humidity}");
lowHumidity.setClearRule(clearHumidityRule);
deviceProfileData.setAlarms(Arrays.asList(highTemperature, lowHumidity));
DeviceProfile savedThermostatDeviceProfile = deviceProfileService.saveDeviceProfile(thermostatDeviceProfile);
createAlarmRules(demoTenant.getId(), savedThermostatDeviceProfile.getId());
DeviceId t1Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
DeviceId t2Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
@ -526,6 +428,130 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
installScripts.createDefaultTenantDashboards(demoTenant.getId(), null);
}
private void createAlarmRules(TenantId tenantId, DeviceProfileId deviceProfileId) {
CalculatedField highTemperature = new CalculatedField();
highTemperature.setName("High Temperature");
highTemperature.setType(CalculatedFieldType.ALARM);
highTemperature.setTenantId(tenantId);
highTemperature.setEntityId(deviceProfileId);
highTemperature.setDebugSettings(DebugSettings.all());
AlarmCalculatedFieldConfiguration highTemperatureConfig = new AlarmCalculatedFieldConfiguration();
highTemperature.setConfiguration(highTemperatureConfig);
Argument temperatureArgument = new Argument();
temperatureArgument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null));
Argument temperatureThresholdArgument = new Argument();
temperatureThresholdArgument.setRefEntityKey(new ReferencedEntityKey("temperatureAlarmThreshold", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE));
temperatureThresholdArgument.setDefaultValue("25");
Argument temperatureAlarmFlagArgument = new Argument();
temperatureAlarmFlagArgument.setRefEntityKey(new ReferencedEntityKey("temperatureAlarmFlag", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE));
highTemperatureConfig.setArguments(Map.of(
"temperature", temperatureArgument,
"temperatureAlarmThreshold", temperatureThresholdArgument,
"temperatureAlarmFlag", temperatureAlarmFlagArgument
));
AlarmRule temperatureRule = new AlarmRule();
SimpleAlarmCondition temperatureCondition = new SimpleAlarmCondition();
AlarmConditionFilter temperatureAlarmFlagFilter = new AlarmConditionFilter();
temperatureAlarmFlagFilter.setArgument("temperatureAlarmFlag");
BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate();
temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
temperatureAlarmFlagAttributePredicate.setValue(new AlarmConditionValue<>(Boolean.TRUE, null));
temperatureAlarmFlagFilter.setPredicate(temperatureAlarmFlagAttributePredicate);
AlarmConditionFilter temperatureFilter = new AlarmConditionFilter();
temperatureFilter.setArgument("temperature");
NumericFilterPredicate temperatureFilterPredicate = new NumericFilterPredicate();
temperatureFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
temperatureFilterPredicate.setValue(new AlarmConditionValue<>(null, "temperatureAlarmThreshold"));
temperatureFilter.setPredicate(temperatureFilterPredicate);
temperatureCondition.setExpression(new SimpleAlarmConditionExpression(List.of(temperatureAlarmFlagFilter, temperatureFilter), ComplexOperation.AND));
temperatureRule.setCondition(temperatureCondition);
temperatureRule.setAlarmDetails("Current temperature = ${temperature}");
highTemperatureConfig.setCreateRules(Map.of(
AlarmSeverity.MAJOR, temperatureRule
));
AlarmRule clearTemperatureRule = new AlarmRule();
SimpleAlarmCondition clearTemperatureCondition = new SimpleAlarmCondition();
AlarmConditionFilter clearTemperatureFilter = new AlarmConditionFilter();
clearTemperatureFilter.setArgument("temperature");
NumericFilterPredicate clearTemperatureFilterPredicate = new NumericFilterPredicate();
clearTemperatureFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL);
clearTemperatureFilterPredicate.setValue(new AlarmConditionValue<>(null, "temperatureAlarmThreshold"));
clearTemperatureFilter.setPredicate(clearTemperatureFilterPredicate);
clearTemperatureCondition.setExpression(new SimpleAlarmConditionExpression(List.of(clearTemperatureFilter), ComplexOperation.AND));
clearTemperatureRule.setCondition(clearTemperatureCondition);
clearTemperatureRule.setAlarmDetails("Current temperature = ${temperature}");
highTemperatureConfig.setClearRule(clearTemperatureRule);
calculatedFieldService.save(highTemperature);
CalculatedField lowHumidity = new CalculatedField();
lowHumidity.setName("Low Humidity");
lowHumidity.setType(CalculatedFieldType.ALARM);
lowHumidity.setTenantId(tenantId);
lowHumidity.setEntityId(deviceProfileId);
lowHumidity.setDebugSettings(DebugSettings.all());
AlarmCalculatedFieldConfiguration lowHumidityConfig = new AlarmCalculatedFieldConfiguration();
lowHumidity.setConfiguration(lowHumidityConfig);
Argument humidityArgument = new Argument();
humidityArgument.setRefEntityKey(new ReferencedEntityKey("humidity", ArgumentType.TS_LATEST, null));
Argument humidityThresholdArgument = new Argument();
humidityThresholdArgument.setRefEntityKey(new ReferencedEntityKey("humidityAlarmThreshold", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE));
humidityThresholdArgument.setDefaultValue("60");
Argument humidityAlarmFlagArgument = new Argument();
humidityAlarmFlagArgument.setRefEntityKey(new ReferencedEntityKey("humidityAlarmFlag", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE));
lowHumidityConfig.setArguments(Map.of(
"humidity", humidityArgument,
"humidityAlarmThreshold", humidityThresholdArgument,
"humidityAlarmFlag", humidityAlarmFlagArgument
));
AlarmRule humidityRule = new AlarmRule();
SimpleAlarmCondition humidityCondition = new SimpleAlarmCondition();
AlarmConditionFilter humidityAlarmFlagAttributeFilter = new AlarmConditionFilter();
humidityAlarmFlagAttributeFilter.setArgument("humidityAlarmFlag");
BooleanFilterPredicate humidityAlarmFlagPredicate = new BooleanFilterPredicate();
humidityAlarmFlagPredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
humidityAlarmFlagPredicate.setValue(new AlarmConditionValue<>(Boolean.TRUE, null));
humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagPredicate);
AlarmConditionFilter humidityFilter = new AlarmConditionFilter();
humidityFilter.setArgument("humidity");
NumericFilterPredicate humidityFilterPredicate = new NumericFilterPredicate();
humidityFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
humidityFilterPredicate.setValue(new AlarmConditionValue<>(null, "humidityAlarmThreshold"));
humidityFilter.setPredicate(humidityFilterPredicate);
humidityCondition.setExpression(new SimpleAlarmConditionExpression(List.of(humidityAlarmFlagAttributeFilter, humidityFilter), ComplexOperation.AND));
humidityRule.setCondition(humidityCondition);
humidityRule.setAlarmDetails("Current humidity = ${humidity}");
lowHumidityConfig.setCreateRules(Map.of(
AlarmSeverity.MINOR, humidityRule
));
AlarmRule clearHumidityRule = new AlarmRule();
SimpleAlarmCondition clearHumidityCondition = new SimpleAlarmCondition();
AlarmConditionFilter clearHumidityFilter = new AlarmConditionFilter();
clearHumidityFilter.setArgument("humidity");
NumericFilterPredicate clearHumidityFilterPredicate = new NumericFilterPredicate();
clearHumidityFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL);
clearHumidityFilterPredicate.setValue(new AlarmConditionValue<>(null, "humidityAlarmThreshold"));
clearHumidityFilter.setPredicate(clearHumidityFilterPredicate);
clearHumidityCondition.setExpression(new SimpleAlarmConditionExpression(List.of(clearHumidityFilter), ComplexOperation.AND));
clearHumidityRule.setCondition(clearHumidityCondition);
clearHumidityRule.setAlarmDetails("Current humidity = ${humidity}");
lowHumidityConfig.setClearRule(clearHumidityRule);
calculatedFieldService.save(lowHumidity);
}
@Override
public void loadSystemWidgets() throws Exception {
installScripts.loadSystemWidgets();
@ -609,6 +635,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
public void onFailure(Throwable t) {
log.warn("[{}] Failed to update attribute [{}] with value [{}]", deviceId, key, value, t);
}
}
private <S> void addTsCallback(ListenableFuture<S> saveFuture, final FutureCallback<S> callback) {

1
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -236,6 +236,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
}
} else if (EntityType.TB_RESOURCE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
tbResourceDataCache.evictResourceData(tenantId, new TbResourceId(componentLifecycleMsg.getEntityId().getId()));
return;
}
eventPublisher.publishEvent(componentLifecycleMsg);

1
application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java

@ -690,6 +690,7 @@ public class AlarmRulesTest extends AbstractControllerTest {
}
// TODO: MSA tests
// TODO: test when attribute or telemetry is deleted without default value - perform calculation not happens
private void checkAlarmResult(CalculatedField calculatedField, Consumer<TbAlarmResult> assertion) {
checkAlarmResult(calculatedField, null, assertion);

160
application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java

@ -77,6 +77,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
@ -431,14 +432,17 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
List<EntityKey> alarmFields = new ArrayList<>();
alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, "type"));
alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, "originatorDisplayName"));
EntityTypeFilter assetTypeFilter = new EntityTypeFilter();
assetTypeFilter.setEntityType(EntityType.ASSET);
AlarmDataQuery assetAlarmQuery = new AlarmDataQuery(assetTypeFilter, pageLink, null, null, null, alarmFields);
PageData<AlarmData> alarmPageData = findAlarmsByQueryAndCheck(assetAlarmQuery, 10);
List<String> retrievedAlarmTypes = alarmPageData.getData().stream().map(Alarm::getType).toList();
List<String> retrievedAlarmTypes = alarmPageData.getData().stream().map(AlarmData::getType).toList();
assertThat(retrievedAlarmTypes).containsExactlyInAnyOrderElementsOf(assetAlarmTypes);
List<String> retrievedAlarmDisplayName = alarmPageData.getData().stream().map(AlarmData::getOriginatorDisplayName).toList();
assertThat(retrievedAlarmDisplayName).containsExactlyInAnyOrderElementsOf(assets.stream().map(Asset::getLabel).toList());
KeyFilter nameFilter = buildStringKeyFilter(EntityKeyType.ENTITY_FIELD, "name", StringFilterPredicate.StringOperation.STARTS_WITH, "Asset1");
List<KeyFilter> keyFilters = Collections.singletonList(nameFilter);
@ -1068,6 +1072,119 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
countByQueryAndCheck(customerEntitiesQuery, 0);
}
@Test
public void testFindDevicesByDisplayName() throws Exception {
loginTenantAdmin();
int numOfDevices = 3;
for (int i = 0; i < numOfDevices; i++) {
Device device = new Device();
String name = "Device" + i;
device.setName(name);
device.setLabel("Device Label " + i);
device.setType("testFindDevicesByDisplayName");
Device savedDevice = doPost("/api/device?accessToken=" + name, device, Device.class);
}
DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceTypes(List.of("testFindDevicesByDisplayName"));
filter.setDeviceNameFilter("");
KeyFilter displayNameFilter = getEntityFieldEqualFilter("displayName", "Device Label " + 0);
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "displayName"), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = List.of(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), new EntityKey(EntityKeyType.ENTITY_FIELD, "displayName"));
// all devices with ownerName = TEST TENANT
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), Collections.emptyList());
checkEntitiesByQuery(query, numOfDevices, (i, entity) -> {
String name = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("name", new TsValue(0, "Invalid")).getValue();
String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
Assert.assertEquals("Device" + i, name);
Assert.assertEquals("Device Label " + i, displayName);
});
// all devices with ownerName = TEST TENANT
EntityDataQuery displayNameFilterQuery = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), List.of(displayNameFilter));
checkEntitiesByQuery(displayNameFilterQuery, 1, (i, entity) -> {
String name = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("name", new TsValue(0, "Invalid")).getValue();
String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
Assert.assertEquals("Device" + i, name);
Assert.assertEquals("Device Label " + i, displayName);
});
}
@Test
public void testFindUsersByDisplayName() throws Exception {
loginTenantAdmin();
User userA = new User();
userA.setAuthority(Authority.TENANT_ADMIN);
userA.setFirstName("John");
userA.setLastName("Doe");
userA.setEmail("john.doe@tb.org");
userA = doPost("/api/user", userA, User.class);
var aId = userA.getId();
User userB = new User();
userB.setAuthority(Authority.TENANT_ADMIN);
userB.setFirstName("John");
userB.setEmail("john@tb.org");
userB = doPost("/api/user", userB, User.class);
var bId = userB.getId();
User userC = new User();
userC.setAuthority(Authority.TENANT_ADMIN);
userC.setLastName("Doe");
userC.setEmail("doe@tb.org");
userC = doPost("/api/user", userC, User.class);
var cId = userC.getId();
User userD = new User();
userD.setAuthority(Authority.TENANT_ADMIN);
userD.setEmail("noname@tb.org");
userD = doPost("/api/user", userD, User.class);
var dId = userD.getId();
EntityTypeFilter filter = new EntityTypeFilter();
filter.setEntityType(EntityType.USER);
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "displayName"), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = List.of(new EntityKey(EntityKeyType.ENTITY_FIELD, "displayName"));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), List.of(getEntityFieldEqualFilter("displayName", "John Doe")));
checkEntitiesByQuery(query, 1, (i, entity) -> {
Assert.assertEquals(aId, entity.getEntityId());
String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
Assert.assertEquals("John Doe", displayName);
});
query = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), List.of(getEntityFieldEqualFilter("displayName", "John")));
checkEntitiesByQuery(query, 1, (i, entity) -> {
Assert.assertEquals(bId, entity.getEntityId());
String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
Assert.assertEquals("John", displayName);
});
query = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), List.of(getEntityFieldEqualFilter("displayName", "Doe")));
checkEntitiesByQuery(query, 1, (i, entity) -> {
Assert.assertEquals(cId, entity.getEntityId());
String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
Assert.assertEquals("Doe", displayName);
});
query = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), List.of(getEntityFieldEqualFilter("displayName", "noname@tb.org")));
checkEntitiesByQuery(query, 1, (i, entity) -> {
Assert.assertEquals(dId, entity.getEntityId());
String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
Assert.assertEquals("noname@tb.org", displayName);
});
}
@Test
public void testFindDevicesByOwnerNameAndOwnerType() throws Exception {
loginTenantAdmin();
@ -1105,19 +1222,30 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
// all devices with ownerName = TEST TENANT
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, tenantOwnerNameFilter));
checkEntitiesByQuery(query, numOfDevices, TEST_TENANT_NAME, "TENANT");
BiConsumer<Integer, EntityData> checkFunction = (i, entity) -> {
String name = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("name", new TsValue(0, "Invalid")).getValue();
String ownerName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("ownerName", new TsValue(0, "Invalid")).getValue();
String ownerType = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("ownerType", new TsValue(0, "Invalid")).getValue();
String alarmActiveTime = entity.getLatest().get(EntityKeyType.ATTRIBUTE).getOrDefault("alarmActiveTime", new TsValue(0, "-1")).getValue();
Assert.assertEquals("Device" + i, name);
Assert.assertEquals(TEST_TENANT_NAME, ownerName);
Assert.assertEquals("TENANT", ownerType);
Assert.assertEquals("1" + i, alarmActiveTime);
};
checkEntitiesByQuery(query, numOfDevices, checkFunction);
// all devices with wrong ownerName
EntityDataQuery wrongTenantNameQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, wrongOwnerNameFilter));
checkEntitiesByQuery(wrongTenantNameQuery, 0, null, null);
checkEntitiesByQuery(wrongTenantNameQuery, 0, null);
// all devices with owner type = TENANT
EntityDataQuery tenantEntitiesQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, tenantOwnerTypeFilter));
checkEntitiesByQuery(tenantEntitiesQuery, numOfDevices, TEST_TENANT_NAME, "TENANT");
checkEntitiesByQuery(tenantEntitiesQuery, numOfDevices, checkFunction);
// all devices with owner type = CUSTOMER
EntityDataQuery customerEntitiesQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, customerOwnerTypeFilter));
checkEntitiesByQuery(customerEntitiesQuery, 0, null, null);
checkEntitiesByQuery(customerEntitiesQuery, 0, null);
}
@Test
@ -1163,6 +1291,28 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
findByQueryAndCheck(query, 0);
}
private void checkEntitiesByQuery(EntityDataQuery query, int expectedNumOfDevices, BiConsumer<Integer,EntityData> checkFunction) throws Exception {
await()
.alias("data by query")
.atMost(30, TimeUnit.SECONDS)
.until(() -> {
var data = findByQuery(query);
var loadedEntities = new ArrayList<>(data.getData());
return loadedEntities.size() == expectedNumOfDevices;
});
if (expectedNumOfDevices == 0) {
return;
}
var data = findByQuery(query);
var loadedEntities = new ArrayList<>(data.getData());
Assert.assertEquals(expectedNumOfDevices, loadedEntities.size());
for (int i = 0; i < expectedNumOfDevices; i++) {
checkFunction.accept(i, loadedEntities.get(i));
}
}
private void checkEntitiesByQuery(EntityDataQuery query, int expectedNumOfDevices, String expectedOwnerName, String expectedOwnerType) throws Exception {
await()
.alias("data by query")

5
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java

@ -38,6 +38,11 @@ public class AlarmInfo extends Alarm {
@Schema(description = "Alarm originator label", example = "Thermostat label")
private String originatorLabel;
@Getter
@Setter
@Schema(description = "Originator display name", example = "Thermostat")
private String originatorDisplayName;
@Getter
@Setter
@Schema(description = "Alarm assignee")

4
common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/SimpleAlarmConditionExpression.java

@ -17,11 +17,15 @@ package org.thingsboard.server.common.data.alarm.rule.condition.expression;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SimpleAlarmConditionExpression implements AlarmConditionExpression {
@Valid

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java

@ -26,6 +26,7 @@ import java.util.List;
@Schema
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated
public class AlarmCondition implements Serializable {
@Valid

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java

@ -26,6 +26,7 @@ import java.io.Serializable;
@Schema
@Data
@Deprecated
public class AlarmConditionFilter implements Serializable {
@Valid

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java

@ -23,6 +23,7 @@ import java.io.Serializable;
@Schema
@Data
@Deprecated
public class AlarmConditionFilterKey implements Serializable {
@Schema(description = "The key type", example = "TIME_SERIES")

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionKeyType.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.device.profile;
@Deprecated
public enum AlarmConditionKeyType {
ATTRIBUTE,
TIME_SERIES,

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionSpec.java

@ -31,6 +31,7 @@ import java.io.Serializable;
@JsonSubTypes.Type(value = SimpleAlarmConditionSpec.class, name = "SIMPLE"),
@JsonSubTypes.Type(value = DurationAlarmConditionSpec.class, name = "DURATION"),
@JsonSubTypes.Type(value = RepeatingAlarmConditionSpec.class, name = "REPEATING")})
@Deprecated
public interface AlarmConditionSpec extends Serializable {
@JsonIgnore

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionSpecType.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.device.profile;
@Deprecated
public enum AlarmConditionSpecType {
SIMPLE,

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java

@ -25,6 +25,7 @@ import java.io.Serializable;
@Schema
@Data
@Deprecated
public class AlarmRule implements Serializable {
@Valid

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java

@ -31,6 +31,7 @@ import java.io.Serializable;
@JsonSubTypes.Type(value = AnyTimeSchedule.class, name = "ANY_TIME"),
@JsonSubTypes.Type(value = SpecificTimeSchedule.class, name = "SPECIFIC_TIME"),
@JsonSubTypes.Type(value = CustomTimeSchedule.class, name = "CUSTOM")})
@Deprecated
public interface AlarmSchedule extends Serializable {
AlarmScheduleType getType();

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmScheduleType.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.device.profile;
@Deprecated
public enum AlarmScheduleType {
ANY_TIME,

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java

@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile;
import org.thingsboard.server.common.data.query.DynamicValue;
@Deprecated
public class AnyTimeSchedule implements AlarmSchedule {
@Override

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.query.DynamicValue;
import java.util.List;
@Data
@Deprecated
public class CustomTimeSchedule implements AlarmSchedule {
private String timezone;

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeScheduleItem.java

@ -20,6 +20,7 @@ import lombok.Data;
import java.io.Serializable;
@Data
@Deprecated
public class CustomTimeScheduleItem implements Serializable {
private boolean enabled;

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java

@ -28,6 +28,7 @@ import java.util.TreeMap;
@Schema
@Data
@Deprecated
public class DeviceProfileAlarm implements Serializable {
@Schema(description = "String value representing the alarm rule id", example = "highTemperatureAlarmID")

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java

@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated
public class DurationAlarmConditionSpec implements AlarmConditionSpec {
private TimeUnit unit;

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.query.FilterPredicateValue;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated
public class RepeatingAlarmConditionSpec implements AlarmConditionSpec {
private FilterPredicateValue<Integer> predicate;

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SimpleAlarmConditionSpec.java

@ -20,6 +20,7 @@ import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated
public class SimpleAlarmConditionSpec implements AlarmConditionSpec {
@Override
public AlarmConditionSpecType getType() {

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.query.DynamicValue;
import java.util.Set;
@Data
@Deprecated
public class SpecificTimeSchedule implements AlarmSchedule {
private String timezone;

2
common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java

@ -38,7 +38,7 @@ public enum TbMsgType {
ENTITY_UNASSIGNED("Entity Unassigned"),
ATTRIBUTES_UPDATED("Attributes Updated"),
ATTRIBUTES_DELETED("Attributes Deleted"),
ALARM,
ALARM("Alarm"),
ALARM_ACK("Alarm Acknowledged"),
ALARM_CLEAR("Alarm Cleared"),
ALARM_DELETE,

22
common/edqs/src/main/java/org/thingsboard/server/edqs/data/BaseEntityData.java

@ -20,6 +20,7 @@ import lombok.Setter;
import lombok.ToString;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edqs.fields.EntityFields;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.permission.QueryContext;
@ -139,11 +140,32 @@ public abstract class BaseEntityData<T extends EntityFields> implements EntityDa
case "name" -> getEntityName();
case "ownerName" -> getOwnerName();
case "ownerType" -> getOwnerType();
case "displayName" -> getDisplayName();
case "entityType" -> Optional.ofNullable(getEntityType()).map(EntityType::name).orElse("");
default -> fields.getAsString(name);
};
}
public String getDisplayName(){
return switch (getEntityType()) {
case DEVICE, ASSET -> StringUtils.isNotBlank(fields.getLabel()) ? fields.getLabel() : fields.getName();
case USER -> {
boolean firstNameSet = StringUtils.isNotBlank(fields.getFirstName());
boolean lastNameSet = StringUtils.isNotBlank(fields.getLastName());
if(firstNameSet && lastNameSet) {
yield fields.getFirstName() + " " + fields.getLastName();
} else if(firstNameSet) {
yield fields.getFirstName();
} else if (lastNameSet) {
yield fields.getLastName();
} else {
yield fields.getEmail();
}
}
default -> fields.getName();
};
}
public String getEntityName() {
return getFields().getName();
}

1
common/edqs/src/main/java/org/thingsboard/server/edqs/data/DeviceData.java

@ -18,6 +18,7 @@ package org.thingsboard.server.edqs.data;
import lombok.ToString;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edqs.fields.DeviceFields;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.edqs.DataPoint;

1
dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java

@ -121,6 +121,7 @@ public class AlarmDataAdapter {
AlarmData alarmData = new AlarmData(alarm, entityId);
alarmData.setOriginatorName(originatorName);
alarmData.setOriginatorLabel(originatorLabel);
alarmData.setOriginatorDisplayName(StringUtils.isBlank(originatorLabel) ? originatorName : originatorLabel);
if (alarm.getAssigneeId() != null) {
alarmData.setAssignee(new AlarmAssignee(alarm.getAssigneeId(), assigneeFirstName, assigneeLastName, assigneeEmail));
}

27
dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java

@ -65,6 +65,7 @@ public class EntityKeyMapping {
public static final String NAME = "name";
public static final String TYPE = "type";
public static final String LABEL = "label";
public static final String DISPLAY_NAME = "displayName";
public static final String FIRST_NAME = "firstName";
public static final String LAST_NAME = "lastName";
public static final String EMAIL = "email";
@ -83,6 +84,8 @@ public class EntityKeyMapping {
public static final String SERVICE_ID = "serviceId";
public static final String OWNER_NAME = "ownerName";
public static final String OWNER_TYPE = "ownerType";
public static final String LABELED_ENTITY_DISPLAY_NAME_SELECT_QUERY = "COALESCE(NULLIF(TRIM(e." + LABEL + "), ''), e." + NAME + ")";
public static final String USER_DISPLAY_NAME_SELECT_QUERY = "COALESCE(NULLIF(TRIM(CONCAT_WS(' ', e.first_name, e.last_name)), ''), e.email)";
public static final String OWNER_NAME_SELECT_QUERY = "case when e.customer_id = '" + NULL_UUID + "' " +
"then (select title from tenant where id = e.tenant_id) " +
"else (select title from customer where id = e.customer_id) end";
@ -94,6 +97,16 @@ public class EntityKeyMapping {
OWNER_NAME, OWNER_NAME_SELECT_QUERY,
OWNER_TYPE, OWNER_TYPE_SELECT_QUERY
);
public static final Map<String, String> labeledPropertiesFunctions = Map.of(
OWNER_NAME, OWNER_NAME_SELECT_QUERY,
OWNER_TYPE, OWNER_TYPE_SELECT_QUERY,
DISPLAY_NAME, LABELED_ENTITY_DISPLAY_NAME_SELECT_QUERY
);
public static final Map<String, String> userPropertiesFunctions = Map.of(
OWNER_NAME, OWNER_NAME_SELECT_QUERY,
OWNER_TYPE, OWNER_TYPE_SELECT_QUERY,
DISPLAY_NAME, USER_DISPLAY_NAME_SELECT_QUERY
);
public static final Map<String, String> queueStatsPropertiesFunctions = Map.of(NAME, QUEUE_STATS_NAME_QUERY);
public static final List<String> typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO);
@ -153,20 +166,24 @@ public class EntityKeyMapping {
Map<String, String> contactBasedAliases = new HashMap<>();
contactBasedAliases.put(NAME, TITLE);
contactBasedAliases.put(LABEL, TITLE);
contactBasedAliases.put(DISPLAY_NAME, TITLE);
aliases.put(EntityType.TENANT, contactBasedAliases);
aliases.put(EntityType.CUSTOMER, contactBasedAliases);
aliases.put(EntityType.DASHBOARD, contactBasedAliases);
Map<String, String> deviceAndAssetAliases = new HashMap<>();
deviceAndAssetAliases.put(TITLE, NAME);
aliases.put(EntityType.DEVICE, deviceAndAssetAliases);
aliases.put(EntityType.ASSET, deviceAndAssetAliases);
Map<String, String> commonEntityAliases = new HashMap<>();
commonEntityAliases.put(TITLE, NAME);
aliases.put(EntityType.DEVICE, commonEntityAliases);
aliases.put(EntityType.ASSET, commonEntityAliases);
commonEntityAliases.put(DISPLAY_NAME, NAME);
aliases.put(EntityType.ENTITY_VIEW, commonEntityAliases);
aliases.put(EntityType.WIDGETS_BUNDLE, commonEntityAliases);
propertiesFunctions.put(EntityType.DEVICE, ownerPropertiesFunctions);
propertiesFunctions.put(EntityType.ASSET, ownerPropertiesFunctions);
propertiesFunctions.put(EntityType.DEVICE, labeledPropertiesFunctions);
propertiesFunctions.put(EntityType.ASSET, labeledPropertiesFunctions);
propertiesFunctions.put(EntityType.ENTITY_VIEW, ownerPropertiesFunctions);
propertiesFunctions.put(EntityType.USER, ownerPropertiesFunctions);
propertiesFunctions.put(EntityType.USER, userPropertiesFunctions);
propertiesFunctions.put(EntityType.DASHBOARD, ownerPropertiesFunctions);
propertiesFunctions.put(EntityType.QUEUE_STATS, queueStatsPropertiesFunctions);

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.rule.engine.profile;
@Deprecated
public enum AlarmEvalResult {
FALSE, NOT_YET_TRUE, TRUE;

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java

@ -55,6 +55,7 @@ import static org.thingsboard.server.common.data.StringUtils.splitByCommaWithout
@Data
@Slf4j
class AlarmRuleState {
private final AlarmSeverity severity;

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java

@ -48,6 +48,7 @@ import java.util.function.BiFunction;
@Data
@Slf4j
@Deprecated
class AlarmState {
public static final String ERROR_MSG = "Failed to process alarm rule for Device [%s]: %s";

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DataSnapshot.java

@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Deprecated
class DataSnapshot {
private volatile boolean ready;

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java

@ -71,6 +71,7 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_RE
import static org.thingsboard.server.common.data.msg.TbMsgType.TIMESERIES_UPDATED;
@Slf4j
@Deprecated
class DeviceState {
private final boolean persistState;

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtx.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.rule.engine.profile;
@Deprecated
public interface DynamicPredicateValueCtx {
EntityKeyValue getTenantValue(String key);

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtxImpl.java

@ -29,6 +29,7 @@ import java.util.Optional;
import java.util.concurrent.ExecutionException;
@Slf4j
@Deprecated
public class DynamicPredicateValueCtxImpl implements DynamicPredicateValueCtx {
private final TenantId tenantId;
private CustomerId customerId;

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/EntityKeyValue.java

@ -20,6 +20,7 @@ import lombok.Getter;
import org.thingsboard.server.common.data.kv.DataType;
@EqualsAndHashCode
@Deprecated
class EntityKeyValue {
@Getter

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java

@ -45,6 +45,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
@Deprecated
class ProfileState {
private DeviceProfile deviceProfile;

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/SnapshotUpdate.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import java.util.Set;
@Deprecated
class SnapshotUpdate {
@Getter

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java

@ -51,17 +51,18 @@ import java.util.concurrent.TimeUnit;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
name = "device profile",
name = "device profile (deprecated)", // TODO: add description on why is it deprecated and what to use
customRelations = true,
relationTypes = {"Alarm Created", "Alarm Updated", "Alarm Severity Updated", "Alarm Cleared", "Success", "Failure"},
version = 1,
configClazz = TbDeviceProfileNodeConfiguration.class,
nodeDescription = "Process device messages based on device profile settings",
nodeDescription = "Process device messages based on device profile settings (deprecated)",
nodeDetails = "Create and clear alarms based on alarm rules defined in device profile. The output relation type is either " +
"'Alarm Created', 'Alarm Updated', 'Alarm Severity Updated' and 'Alarm Cleared' or simply 'Success' if no alarms were affected.",
configDirective = "tbActionNodeDeviceProfileConfig",
docUrl = "https://thingsboard.io/docs/user-guide/rule-engine-2-0/nodes/action/device-profile/"
)
@Deprecated
public class TbDeviceProfileNode implements TbNode {
private TbDeviceProfileNodeConfiguration config;

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeConfiguration.java

@ -21,6 +21,7 @@ import org.thingsboard.rule.engine.api.NodeConfiguration;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated
public class TbDeviceProfileNodeConfiguration implements NodeConfiguration<TbDeviceProfileNodeConfiguration> {
private boolean persistAlarmRulesState;

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/state/PersistedAlarmRuleState.java

@ -22,6 +22,7 @@ import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Deprecated
public class PersistedAlarmRuleState {
private long lastEventTs;

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/state/PersistedAlarmState.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import java.util.Map;
@Data
@Deprecated
public class PersistedAlarmState {
private Map<AlarmSeverity, PersistedAlarmRuleState> createRuleStates;

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/state/PersistedDeviceState.java

@ -20,6 +20,7 @@ import lombok.Data;
import java.util.Map;
@Data
@Deprecated
public class PersistedDeviceState {
Map<String, PersistedAlarmState> alarmStates;

2
ui-ngx/src/app/core/http/entity.service.ts

@ -817,6 +817,7 @@ export class EntityService {
switch (entityType) {
case EntityType.USER:
entityFieldKeys.push(entityFields.name.keyName);
entityFieldKeys.push(entityFields.displayName.keyName);
entityFieldKeys.push(entityFields.email.keyName);
entityFieldKeys.push(entityFields.firstName.keyName);
entityFieldKeys.push(entityFields.lastName.keyName);
@ -846,6 +847,7 @@ export class EntityService {
case EntityType.EDGE:
case EntityType.ASSET:
entityFieldKeys.push(entityFields.name.keyName);
entityFieldKeys.push(entityFields.displayName.keyName);
entityFieldKeys.push(entityFields.type.keyName);
entityFieldKeys.push(entityFields.label.keyName);
entityFieldKeys.push(entityFields.ownerName.keyName);

2
ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html

@ -60,7 +60,7 @@
#entityAutocomplete="matAutocomplete"
[displayWith]="displayEntityFn">
<mat-option *ngFor="let entity of filteredEntities | async" [value]="entity">
<span [innerHTML]="entity.name | highlight:searchText:true:'ig'"></span>
<span [innerHTML]="displayEntityFn(entity) | highlight:searchText:true:'ig'"></span>
</mat-option>
<mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null">
<div (click)="$event.stopPropagation()">

8
ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts

@ -22,7 +22,7 @@ import { catchError, debounceTime, map, share, switchMap, tap } from 'rxjs/opera
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { AliasEntityType, EntityType } from '@shared/models/entity-type.models';
import { BaseData } from '@shared/models/base-data';
import { BaseData, getEntityDisplayName } from '@shared/models/base-data';
import { EntityId } from '@shared/models/id/entity-id';
import { EntityService } from '@core/http/entity.service';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
@ -138,6 +138,10 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
@coerceArray()
additionalClasses: Array<string>;
@Input()
@coerceBoolean()
useEntityDisplayName = false;
@Output()
entityChanged = new EventEmitter<BaseData<EntityId>>();
@ -395,7 +399,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
}
displayEntityFn(entity?: BaseData<EntityId>): string | undefined {
return entity ? entity.name : undefined;
return entity ? (this.useEntityDisplayName ? getEntityDisplayName(entity) : entity.name) : undefined;
}
private fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {

1
ui-ngx/src/app/shared/components/entity/entity-list-select.component.html

@ -36,6 +36,7 @@
*ngIf="modelValue.entityType"
[required]="required"
[entityType]="modelValue.entityType"
[useEntityDisplayName]="useEntityDisplayName"
formControlName="entityIds">
</tb-entity-list>
</div>

3
ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts

@ -68,6 +68,9 @@ export class EntityListSelectComponent implements ControlValueAccessor, OnInit {
@Input()
additionEntityTypes: {[key in string]: string} = {};
@Input({transform: booleanAttribute})
useEntityDisplayName = false;
displayEntityTypeSelect: boolean;
private defaultEntityType: EntityType | AliasEntityType = null;

4
ui-ngx/src/app/shared/components/entity/entity-list.component.html

@ -28,7 +28,7 @@
class="tb-chip-row-ellipsis"
[removable]="!disabled"
(removed)="remove(entity)">
{{entity.name}}
{{ displayEntityFn(entity) }}
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
</mat-chip-row>
<input matInput type="text" placeholder="{{ !disabled ? placeholderText : '' }}"
@ -51,7 +51,7 @@
class="tb-autocomplete"
[displayWith]="displayEntityFn">
<mat-option *ngFor="let entity of filteredEntities | async" [value]="entity">
<span [innerHTML]="entity.name | highlight:searchText"></span>
<span [innerHTML]="displayEntityFn(entity) | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null">
<div (click)="$event.stopPropagation()">

8
ui-ngx/src/app/shared/components/entity/entity-list.component.ts

@ -39,7 +39,7 @@ import { Observable } from 'rxjs';
import { filter, map, mergeMap, share, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { EntityType } from '@shared/models/entity-type.models';
import { BaseData } from '@shared/models/base-data';
import { BaseData, getEntityDisplayName } from '@shared/models/base-data';
import { EntityId } from '@shared/models/id/entity-id';
import { EntityService } from '@core/http/entity.service';
import { MatAutocomplete } from '@angular/material/autocomplete';
@ -125,6 +125,10 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, OnChan
@coerceBoolean()
allowCreateNew: boolean;
@Input()
@coerceBoolean()
useEntityDisplayName = false;
@Output()
createNew = new EventEmitter<string>();
@ -277,7 +281,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, OnChan
}
public displayEntityFn(entity?: BaseData<EntityId>): string | undefined {
return entity ? entity.name : undefined;
return entity ? (this.useEntityDisplayName ? getEntityDisplayName(entity) : entity.name) : undefined;
}
private fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {

1
ui-ngx/src/app/shared/components/entity/entity-select.component.html

@ -33,6 +33,7 @@
[appearance]="appearance"
[required]="required"
[entityType]="modelValue.entityType"
[useEntityDisplayName]="useEntityDisplayName"
formControlName="entityId">
</tb-entity-autocomplete>
</div>

4
ui-ngx/src/app/shared/components/entity/entity-select.component.ts

@ -62,6 +62,10 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Input()
@coerceBoolean()
useEntityDisplayName = false;
displayEntityTypeSelect: boolean;
AliasEntityType = AliasEntityType;

7
ui-ngx/src/app/shared/models/alarm.models.ts

@ -140,6 +140,7 @@ export interface AlarmCommentInfo extends AlarmComment {
export interface AlarmInfo extends Alarm {
originatorName: string;
originatorLabel: string;
originatorDisplayName?: string;
assignee: AlarmAssignee;
}
@ -172,6 +173,7 @@ export const simulatedAlarm: AlarmInfo = {
clearTs: 0,
assignTs: 0,
originatorName: 'Simulated',
originatorDisplayName: 'Simulated',
originatorLabel: 'Simulated',
assignee: {
firstName: '',
@ -242,6 +244,11 @@ export const alarmFields: {[fieldName: string]: AlarmField} = {
value: 'originatorName',
name: 'alarm.originator'
},
originatorDisplayName: {
keyName: 'originatorDisplayName',
value: 'originatorDisplayName',
name: 'alarm.originator'
},
originatorLabel: {
keyName: 'originatorLabel',
value: 'originatorLabel',

13
ui-ngx/src/app/shared/models/base-data.ts

@ -16,7 +16,9 @@
import { EntityId } from '@shared/models/id/entity-id';
import { HasUUID } from '@shared/models/id/has-uuid';
import { isDefinedAndNotNull } from '@core/utils';
import { isDefinedAndNotNull, isNotEmptyStr } from '@core/utils';
import { EntityType } from '@shared/models/entity-type.models';
import { User } from '@shared/models/user.model';
export declare type HasId = EntityId | HasUUID;
@ -49,3 +51,12 @@ export function hasIdEquals(id1: HasId, id2: HasId): boolean {
return id1 === id2;
}
}
export function getEntityDisplayName(entity: BaseData<EntityId>): string {
if (entity?.id?.entityType === EntityType.USER) {
const user = entity as User;
const userName = (user?.firstName ?? '') + " " + (user?.lastName ?? '');
return isNotEmptyStr(userName) ? userName.trim() : entity?.name;
}
return isNotEmptyStr(entity?.label) ? entity.label : entity?.name;
}

5
ui-ngx/src/app/shared/models/entity.models.ts

@ -163,6 +163,11 @@ export const entityFields: {[fieldName: string]: EntityField} = {
name: 'entity-field.label',
value: 'label'
},
displayName: {
keyName: 'displayName',
name: 'entity-field.name',
value: 'name'
},
queueName: {
keyName: 'queueName',
name: 'entity-field.queue-name',

Loading…
Cancel
Save