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 "externalId": null
}, },
"metadata": { "metadata": {
"firstNodeIndex": 0, "firstNodeIndex": 2,
"nodes": [ "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", "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries", "name": "Save Timeseries",
"configurationVersion": 1, "configurationVersion": 1,
@ -41,13 +23,12 @@
"type": "ON_EVERY_MESSAGE" "type": "ON_EVERY_MESSAGE"
} }
}, },
"externalId": null "additionalInfo": {
"layoutX": 823,
"layoutY": 157
}
}, },
{ {
"additionalInfo": {
"layoutX": 824,
"layoutY": 52
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes", "name": "Save Client Attributes",
"configurationVersion": 3, "configurationVersion": 3,
@ -60,25 +41,23 @@
"sendAttributesUpdatedNotification": false, "sendAttributesUpdatedNotification": false,
"updateAttributesOnlyOnValueChange": true "updateAttributesOnlyOnValueChange": true
}, },
"externalId": null "additionalInfo": {
"layoutX": 824,
"layoutY": 52
}
}, },
{ {
"additionalInfo": {
"layoutX": 347,
"layoutY": 149
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch", "name": "Message Type Switch",
"configuration": { "configuration": {
"version": 0 "version": 0
}, },
"externalId": null "additionalInfo": {
"layoutX": 347,
"layoutY": 149
}
}, },
{ {
"additionalInfo": {
"layoutX": 825,
"layoutY": 266
},
"type": "org.thingsboard.rule.engine.action.TbLogNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device", "name": "Log RPC from Device",
"configuration": { "configuration": {
@ -86,13 +65,12 @@
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "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);" "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", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other", "name": "Log Other",
"configuration": { "configuration": {
@ -100,97 +78,92 @@
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "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);" "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}, },
"externalId": null
},
{
"additionalInfo": { "additionalInfo": {
"layoutX": 824, "layoutX": 824,
"layoutY": 466 "layoutY": 378
}, }
},
{
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request", "name": "RPC Call Request",
"configuration": { "configuration": {
"timeoutInSeconds": 60 "timeoutInSeconds": 60
}, },
"externalId": null "additionalInfo": {
"layoutX": 824,
"layoutY": 466
}
}, },
{ {
"additionalInfo": {
"layoutX": 1126,
"layoutY": 104
},
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode", "type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud", "name": "Push to cloud",
"configuration": { "configuration": {
"scope": "CLIENT_SCOPE" "scope": "CLIENT_SCOPE"
}, },
"externalId": null "additionalInfo": {
"layoutX": 1126,
"layoutY": 104
}
}, },
{ {
"additionalInfo": {
"layoutX": 826,
"layoutY": 601
},
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode", "type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud", "name": "Push to cloud",
"configuration": { "configuration": {
"scope": "SERVER_SCOPE" "scope": "SERVER_SCOPE"
}, },
"externalId": null "additionalInfo": {
"layoutX": 826,
"layoutY": 601
}
} }
], ],
"connections": [ "connections": [
{ {
"fromIndex": 0, "fromIndex": 0,
"toIndex": 3, "toIndex": 6,
"type": "Success" "type": "Success"
}, },
{ {
"fromIndex": 1, "fromIndex": 1,
"toIndex": 7, "toIndex": 6,
"type": "Success" "type": "Success"
}, },
{ {
"fromIndex": 2, "fromIndex": 2,
"toIndex": 7, "toIndex": 0,
"type": "Success"
},
{
"fromIndex": 3,
"toIndex": 1,
"type": "Post telemetry" "type": "Post telemetry"
}, },
{ {
"fromIndex": 3, "fromIndex": 2,
"toIndex": 2, "toIndex": 1,
"type": "Post attributes" "type": "Post attributes"
}, },
{ {
"fromIndex": 3, "fromIndex": 2,
"toIndex": 4, "toIndex": 3,
"type": "RPC Request from Device" "type": "RPC Request from Device"
}, },
{ {
"fromIndex": 3, "fromIndex": 2,
"toIndex": 5, "toIndex": 4,
"type": "Other" "type": "Other"
}, },
{ {
"fromIndex": 3, "fromIndex": 2,
"toIndex": 6, "toIndex": 5,
"type": "RPC Request to Device" "type": "RPC Request to Device"
}, },
{ {
"fromIndex": 3, "fromIndex": 2,
"toIndex": 8, "toIndex": 7,
"type": "Attributes Deleted" "type": "Attributes Deleted"
}, },
{ {
"fromIndex": 3, "fromIndex": 2,
"toIndex": 8, "toIndex": 7,
"type": "Attributes Updated" "type": "Attributes Updated"
} }
], ],
"ruleChainConnections": null "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": [], "resources": [],
"templateHtml": "<tb-entities-table-widget \n [ctx]=\"ctx\">\n</tb-entities-table-widget>", "templateHtml": "<tb-entities-table-widget \n [ctx]=\"ctx\">\n</tb-entities-table-widget>",
"templateCss": "", "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", "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",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-entities-table-widget-settings", "settingsDirective": "tb-entities-table-widget-settings",
"dataKeySettingsDirective": "tb-entities-table-key-settings", "dataKeySettingsDirective": "tb-entities-table-key-settings",
"hasBasicMode": true, "hasBasicMode": true,
"basicModeDirective": "tb-entities-table-basic-config", "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}" "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": [ "resources": [
{ {
"link": "/api/images/system/entities_table_system_widget_image.png", "link": "/api/images/system/entities_table_system_widget_image.png",
@ -36,5 +30,10 @@
"data": "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", "data": "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",
"public": true "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 "configuration": null
}, },
"metadata": { "metadata": {
"firstNodeIndex": 6, "firstNodeIndex": 2,
"nodes": [ "nodes": [
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 822, "layoutX": 824,
"layoutY": 294 "layoutY": 156
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries", "name": "Save Timeseries",
@ -30,8 +30,8 @@
}, },
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 824, "layoutX": 825,
"layoutY": 221 "layoutY": 52
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes", "name": "Save Client Attributes",
@ -48,8 +48,8 @@
}, },
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 494, "layoutX": 347,
"layoutY": 309 "layoutY": 149
}, },
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch", "name": "Message Type Switch",
@ -59,8 +59,8 @@
}, },
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 824, "layoutX": 825,
"layoutY": 383 "layoutY": 266
}, },
"type": "org.thingsboard.rule.engine.action.TbLogNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device", "name": "Log RPC from Device",
@ -72,8 +72,8 @@
}, },
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 823, "layoutX": 825,
"layoutY": 444 "layoutY": 379
}, },
"type": "org.thingsboard.rule.engine.action.TbLogNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other", "name": "Log Other",
@ -85,27 +85,14 @@
}, },
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 822, "layoutX": 825,
"layoutY": 507 "layoutY": 468
}, },
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request", "name": "RPC Call Request",
"configuration": { "configuration": {
"timeoutInSeconds": 60 "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": [ "connections": [
@ -133,11 +120,6 @@
"fromIndex": 2, "fromIndex": 2,
"toIndex": 5, "toIndex": 5,
"type": "RPC Request to Device" "type": "RPC Request to Device"
},
{
"fromIndex": 6,
"toIndex": 2,
"type": "Success"
} }
], ],
"ruleChainConnections": null "ruleChainConnections": null

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

@ -9,7 +9,7 @@
"configuration": null "configuration": null
}, },
"metadata": { "metadata": {
"firstNodeIndex": 6, "firstNodeIndex": 2,
"nodes": [ "nodes": [
{ {
"additionalInfo": { "additionalInfo": {
@ -92,27 +92,9 @@
"configuration": { "configuration": {
"timeoutInSeconds": 60 "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": [ "connections": [
{
"fromIndex": 6,
"toIndex": 2,
"type": "Success"
},
{ {
"fromIndex": 2, "fromIndex": 2,
"toIndex": 4, "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) { private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
TbActorRef target = null; TbActorRef target = null;
if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) { 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); log.warn("Message has system tenant id: {}", msg);
} }
} else { } 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.TenantProfile;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.device.profile.AlarmCondition; import org.thingsboard.server.common.data.alarm.rule.AlarmRule;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter; import org.thingsboard.server.common.data.alarm.rule.condition.AlarmConditionValue;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; import org.thingsboard.server.common.data.alarm.rule.condition.SimpleAlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; import org.thingsboard.server.common.data.alarm.rule.condition.expression.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmRule; 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.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; 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.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; 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.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; 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.mobile.app.MobileApp;
import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink; 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.ProcessingStrategy;
import org.thingsboard.server.common.data.queue.ProcessingStrategyType; import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
import org.thingsboard.server.common.data.queue.Queue; 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.TenantProfileData;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
import org.thingsboard.server.dao.attributes.AttributesService; 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.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceConnectivityConfiguration; import org.thingsboard.server.dao.device.DeviceConnectivityConfiguration;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
@ -117,7 +120,7 @@ import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.TreeMap; import java.util.Map;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -155,6 +158,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
private final MobileAppDao mobileAppDao; private final MobileAppDao mobileAppDao;
private final NotificationSettingsService notificationSettingsService; private final NotificationSettingsService notificationSettingsService;
private final NotificationTargetService notificationTargetService; private final NotificationTargetService notificationTargetService;
private final CalculatedFieldService calculatedFieldService;
@Autowired @Autowired
private BCryptPasswordEncoder passwordEncoder; private BCryptPasswordEncoder passwordEncoder;
@ -306,8 +310,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
if (invalidSignKey) { if (invalidSignKey) {
log.warn("WARNING: {}. A new JWT Signing Key has been added automatically. " + log.warn("WARNING: {}. A new JWT Signing Key has been added automatically. " +
"You can change the JWT Signing Key using the Web UI: " + "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); "Navigate to \"System settings -> Security settings\" while logged in as a System Administrator.", warningMessage);
jwtSettings.setTokenSigningKey(generateRandomKey()); jwtSettings.setTokenSigningKey(generateRandomKey());
jwtSettingsService.saveJwtSettings(jwtSettings); jwtSettingsService.saveJwtSettings(jwtSettings);
@ -319,9 +323,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
.filter(mobileApp -> !validateKeyLength(mobileApp.getAppSecret())) .filter(mobileApp -> !validateKeyLength(mobileApp.getAppSecret()))
.forEach(mobileApp -> { .forEach(mobileApp -> {
log.warn("WARNING: The App secret is shorter than 512 bits, which is a security risk. " + 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 [{}]. " + "A new Application Secret has been added automatically for Mobile Application [{}]. " +
"You can change the Application Secret using the Web UI: " + "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()); "Navigate to \"Security settings -> OAuth2 -> Mobile applications\" while logged in as a System Administrator.", mobileApp.getPkgName());
mobileApp.setAppSecret(generateRandomKey()); mobileApp.setAppSecret(generateRandomKey());
mobileAppDao.save(TenantId.SYS_TENANT_ID, mobileApp); 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(), 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(), 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 " + createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "DHT11 Demo Device", "DHT11_DEMO_TOKEN",
"applications that upload data from DHT11 temperature and humidity sensor"); "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 " + createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN",
"Raspberry Pi GPIO control sample application"); "Demo device that is used in Raspberry Pi GPIO control sample application");
DeviceProfile thermostatDeviceProfile = new DeviceProfile(); DeviceProfile thermostatDeviceProfile = new DeviceProfile();
thermostatDeviceProfile.setTenantId(demoTenant.getId()); thermostatDeviceProfile.setTenantId(demoTenant.getId());
@ -398,110 +402,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
deviceProfileData.setProvisionConfiguration(provisionConfiguration); deviceProfileData.setProvisionConfiguration(provisionConfiguration);
thermostatDeviceProfile.setProfileData(deviceProfileData); 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); 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 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(); 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); 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 @Override
public void loadSystemWidgets() throws Exception { public void loadSystemWidgets() throws Exception {
installScripts.loadSystemWidgets(); installScripts.loadSystemWidgets();
@ -609,6 +635,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.warn("[{}] Failed to update attribute [{}] with value [{}]", deviceId, key, value, t); log.warn("[{}] Failed to update attribute [{}] with value [{}]", deviceId, key, value, t);
} }
} }
private <S> void addTsCallback(ListenableFuture<S> saveFuture, final FutureCallback<S> callback) { 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())) { } else if (EntityType.TB_RESOURCE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
tbResourceDataCache.evictResourceData(tenantId, new TbResourceId(componentLifecycleMsg.getEntityId().getId())); tbResourceDataCache.evictResourceData(tenantId, new TbResourceId(componentLifecycleMsg.getEntityId().getId()));
return;
} }
eventPublisher.publishEvent(componentLifecycleMsg); 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: 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) { private void checkAlarmResult(CalculatedField calculatedField, Consumer<TbAlarmResult> assertion) {
checkAlarmResult(calculatedField, null, 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.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -431,14 +432,17 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
List<EntityKey> alarmFields = new ArrayList<>(); List<EntityKey> alarmFields = new ArrayList<>();
alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, "type")); alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, "type"));
alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, "originatorDisplayName"));
EntityTypeFilter assetTypeFilter = new EntityTypeFilter(); EntityTypeFilter assetTypeFilter = new EntityTypeFilter();
assetTypeFilter.setEntityType(EntityType.ASSET); assetTypeFilter.setEntityType(EntityType.ASSET);
AlarmDataQuery assetAlarmQuery = new AlarmDataQuery(assetTypeFilter, pageLink, null, null, null, alarmFields); AlarmDataQuery assetAlarmQuery = new AlarmDataQuery(assetTypeFilter, pageLink, null, null, null, alarmFields);
PageData<AlarmData> alarmPageData = findAlarmsByQueryAndCheck(assetAlarmQuery, 10); 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); 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"); KeyFilter nameFilter = buildStringKeyFilter(EntityKeyType.ENTITY_FIELD, "name", StringFilterPredicate.StringOperation.STARTS_WITH, "Asset1");
List<KeyFilter> keyFilters = Collections.singletonList(nameFilter); List<KeyFilter> keyFilters = Collections.singletonList(nameFilter);
@ -1068,6 +1072,119 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
countByQueryAndCheck(customerEntitiesQuery, 0); 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 @Test
public void testFindDevicesByOwnerNameAndOwnerType() throws Exception { public void testFindDevicesByOwnerNameAndOwnerType() throws Exception {
loginTenantAdmin(); loginTenantAdmin();
@ -1105,19 +1222,30 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
// all devices with ownerName = TEST TENANT // all devices with ownerName = TEST TENANT
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, tenantOwnerNameFilter)); 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 // all devices with wrong ownerName
EntityDataQuery wrongTenantNameQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, wrongOwnerNameFilter)); 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 // all devices with owner type = TENANT
EntityDataQuery tenantEntitiesQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, tenantOwnerTypeFilter)); 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 // all devices with owner type = CUSTOMER
EntityDataQuery customerEntitiesQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, customerOwnerTypeFilter)); EntityDataQuery customerEntitiesQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, customerOwnerTypeFilter));
checkEntitiesByQuery(customerEntitiesQuery, 0, null, null); checkEntitiesByQuery(customerEntitiesQuery, 0, null);
} }
@Test @Test
@ -1163,6 +1291,28 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
findByQueryAndCheck(query, 0); 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 { private void checkEntitiesByQuery(EntityDataQuery query, int expectedNumOfDevices, String expectedOwnerName, String expectedOwnerType) throws Exception {
await() await()
.alias("data by query") .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") @Schema(description = "Alarm originator label", example = "Thermostat label")
private String originatorLabel; private String originatorLabel;
@Getter
@Setter
@Schema(description = "Originator display name", example = "Thermostat")
private String originatorDisplayName;
@Getter @Getter
@Setter @Setter
@Schema(description = "Alarm assignee") @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.Valid;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List; import java.util.List;
@Data @Data
@AllArgsConstructor
@NoArgsConstructor
public class SimpleAlarmConditionExpression implements AlarmConditionExpression { public class SimpleAlarmConditionExpression implements AlarmConditionExpression {
@Valid @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 @Schema
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated
public class AlarmCondition implements Serializable { public class AlarmCondition implements Serializable {
@Valid @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 @Schema
@Data @Data
@Deprecated
public class AlarmConditionFilter implements Serializable { public class AlarmConditionFilter implements Serializable {
@Valid @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 @Schema
@Data @Data
@Deprecated
public class AlarmConditionFilterKey implements Serializable { public class AlarmConditionFilterKey implements Serializable {
@Schema(description = "The key type", example = "TIME_SERIES") @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; package org.thingsboard.server.common.data.device.profile;
@Deprecated
public enum AlarmConditionKeyType { public enum AlarmConditionKeyType {
ATTRIBUTE, ATTRIBUTE,
TIME_SERIES, 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 = SimpleAlarmConditionSpec.class, name = "SIMPLE"),
@JsonSubTypes.Type(value = DurationAlarmConditionSpec.class, name = "DURATION"), @JsonSubTypes.Type(value = DurationAlarmConditionSpec.class, name = "DURATION"),
@JsonSubTypes.Type(value = RepeatingAlarmConditionSpec.class, name = "REPEATING")}) @JsonSubTypes.Type(value = RepeatingAlarmConditionSpec.class, name = "REPEATING")})
@Deprecated
public interface AlarmConditionSpec extends Serializable { public interface AlarmConditionSpec extends Serializable {
@JsonIgnore @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; package org.thingsboard.server.common.data.device.profile;
@Deprecated
public enum AlarmConditionSpecType { public enum AlarmConditionSpecType {
SIMPLE, 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 @Schema
@Data @Data
@Deprecated
public class AlarmRule implements Serializable { public class AlarmRule implements Serializable {
@Valid @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 = AnyTimeSchedule.class, name = "ANY_TIME"),
@JsonSubTypes.Type(value = SpecificTimeSchedule.class, name = "SPECIFIC_TIME"), @JsonSubTypes.Type(value = SpecificTimeSchedule.class, name = "SPECIFIC_TIME"),
@JsonSubTypes.Type(value = CustomTimeSchedule.class, name = "CUSTOM")}) @JsonSubTypes.Type(value = CustomTimeSchedule.class, name = "CUSTOM")})
@Deprecated
public interface AlarmSchedule extends Serializable { public interface AlarmSchedule extends Serializable {
AlarmScheduleType getType(); 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; package org.thingsboard.server.common.data.device.profile;
@Deprecated
public enum AlarmScheduleType { public enum AlarmScheduleType {
ANY_TIME, 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; import org.thingsboard.server.common.data.query.DynamicValue;
@Deprecated
public class AnyTimeSchedule implements AlarmSchedule { public class AnyTimeSchedule implements AlarmSchedule {
@Override @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; import java.util.List;
@Data @Data
@Deprecated
public class CustomTimeSchedule implements AlarmSchedule { public class CustomTimeSchedule implements AlarmSchedule {
private String timezone; 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; import java.io.Serializable;
@Data @Data
@Deprecated
public class CustomTimeScheduleItem implements Serializable { public class CustomTimeScheduleItem implements Serializable {
private boolean enabled; 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 @Schema
@Data @Data
@Deprecated
public class DeviceProfileAlarm implements Serializable { public class DeviceProfileAlarm implements Serializable {
@Schema(description = "String value representing the alarm rule id", example = "highTemperatureAlarmID") @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 @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated
public class DurationAlarmConditionSpec implements AlarmConditionSpec { public class DurationAlarmConditionSpec implements AlarmConditionSpec {
private TimeUnit unit; 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 @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated
public class RepeatingAlarmConditionSpec implements AlarmConditionSpec { public class RepeatingAlarmConditionSpec implements AlarmConditionSpec {
private FilterPredicateValue<Integer> predicate; 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 @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated
public class SimpleAlarmConditionSpec implements AlarmConditionSpec { public class SimpleAlarmConditionSpec implements AlarmConditionSpec {
@Override @Override
public AlarmConditionSpecType getType() { 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; import java.util.Set;
@Data @Data
@Deprecated
public class SpecificTimeSchedule implements AlarmSchedule { public class SpecificTimeSchedule implements AlarmSchedule {
private String timezone; 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"), ENTITY_UNASSIGNED("Entity Unassigned"),
ATTRIBUTES_UPDATED("Attributes Updated"), ATTRIBUTES_UPDATED("Attributes Updated"),
ATTRIBUTES_DELETED("Attributes Deleted"), ATTRIBUTES_DELETED("Attributes Deleted"),
ALARM, ALARM("Alarm"),
ALARM_ACK("Alarm Acknowledged"), ALARM_ACK("Alarm Acknowledged"),
ALARM_CLEAR("Alarm Cleared"), ALARM_CLEAR("Alarm Cleared"),
ALARM_DELETE, 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 lombok.ToString;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType; 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.edqs.fields.EntityFields;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.permission.QueryContext; 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 "name" -> getEntityName();
case "ownerName" -> getOwnerName(); case "ownerName" -> getOwnerName();
case "ownerType" -> getOwnerType(); case "ownerType" -> getOwnerType();
case "displayName" -> getDisplayName();
case "entityType" -> Optional.ofNullable(getEntityType()).map(EntityType::name).orElse(""); case "entityType" -> Optional.ofNullable(getEntityType()).map(EntityType::name).orElse("");
default -> fields.getAsString(name); 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() { public String getEntityName() {
return getFields().getName(); 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 lombok.ToString;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType; 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.edqs.fields.DeviceFields;
import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.edqs.DataPoint; 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 alarmData = new AlarmData(alarm, entityId);
alarmData.setOriginatorName(originatorName); alarmData.setOriginatorName(originatorName);
alarmData.setOriginatorLabel(originatorLabel); alarmData.setOriginatorLabel(originatorLabel);
alarmData.setOriginatorDisplayName(StringUtils.isBlank(originatorLabel) ? originatorName : originatorLabel);
if (alarm.getAssigneeId() != null) { if (alarm.getAssigneeId() != null) {
alarmData.setAssignee(new AlarmAssignee(alarm.getAssigneeId(), assigneeFirstName, assigneeLastName, assigneeEmail)); 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 NAME = "name";
public static final String TYPE = "type"; public static final String TYPE = "type";
public static final String LABEL = "label"; public static final String LABEL = "label";
public static final String DISPLAY_NAME = "displayName";
public static final String FIRST_NAME = "firstName"; public static final String FIRST_NAME = "firstName";
public static final String LAST_NAME = "lastName"; public static final String LAST_NAME = "lastName";
public static final String EMAIL = "email"; public static final String EMAIL = "email";
@ -83,6 +84,8 @@ public class EntityKeyMapping {
public static final String SERVICE_ID = "serviceId"; public static final String SERVICE_ID = "serviceId";
public static final String OWNER_NAME = "ownerName"; public static final String OWNER_NAME = "ownerName";
public static final String OWNER_TYPE = "ownerType"; 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 + "' " + 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) " + "then (select title from tenant where id = e.tenant_id) " +
"else (select title from customer where id = e.customer_id) end"; "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_NAME, OWNER_NAME_SELECT_QUERY,
OWNER_TYPE, OWNER_TYPE_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 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); 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<>(); Map<String, String> contactBasedAliases = new HashMap<>();
contactBasedAliases.put(NAME, TITLE); contactBasedAliases.put(NAME, TITLE);
contactBasedAliases.put(LABEL, TITLE); contactBasedAliases.put(LABEL, TITLE);
contactBasedAliases.put(DISPLAY_NAME, TITLE);
aliases.put(EntityType.TENANT, contactBasedAliases); aliases.put(EntityType.TENANT, contactBasedAliases);
aliases.put(EntityType.CUSTOMER, contactBasedAliases); aliases.put(EntityType.CUSTOMER, contactBasedAliases);
aliases.put(EntityType.DASHBOARD, 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<>(); Map<String, String> commonEntityAliases = new HashMap<>();
commonEntityAliases.put(TITLE, NAME); commonEntityAliases.put(TITLE, NAME);
aliases.put(EntityType.DEVICE, commonEntityAliases); commonEntityAliases.put(DISPLAY_NAME, NAME);
aliases.put(EntityType.ASSET, commonEntityAliases);
aliases.put(EntityType.ENTITY_VIEW, commonEntityAliases); aliases.put(EntityType.ENTITY_VIEW, commonEntityAliases);
aliases.put(EntityType.WIDGETS_BUNDLE, commonEntityAliases); aliases.put(EntityType.WIDGETS_BUNDLE, commonEntityAliases);
propertiesFunctions.put(EntityType.DEVICE, ownerPropertiesFunctions); propertiesFunctions.put(EntityType.DEVICE, labeledPropertiesFunctions);
propertiesFunctions.put(EntityType.ASSET, ownerPropertiesFunctions); propertiesFunctions.put(EntityType.ASSET, labeledPropertiesFunctions);
propertiesFunctions.put(EntityType.ENTITY_VIEW, ownerPropertiesFunctions); propertiesFunctions.put(EntityType.ENTITY_VIEW, ownerPropertiesFunctions);
propertiesFunctions.put(EntityType.USER, ownerPropertiesFunctions); propertiesFunctions.put(EntityType.USER, userPropertiesFunctions);
propertiesFunctions.put(EntityType.DASHBOARD, ownerPropertiesFunctions); propertiesFunctions.put(EntityType.DASHBOARD, ownerPropertiesFunctions);
propertiesFunctions.put(EntityType.QUEUE_STATS, queueStatsPropertiesFunctions); 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; package org.thingsboard.rule.engine.profile;
@Deprecated
public enum AlarmEvalResult { public enum AlarmEvalResult {
FALSE, NOT_YET_TRUE, TRUE; 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 @Data
@Slf4j @Slf4j
class AlarmRuleState { class AlarmRuleState {
private final AlarmSeverity severity; 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 @Data
@Slf4j @Slf4j
@Deprecated
class AlarmState { class AlarmState {
public static final String ERROR_MSG = "Failed to process alarm rule for Device [%s]: %s"; 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.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@Deprecated
class DataSnapshot { class DataSnapshot {
private volatile boolean ready; 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; import static org.thingsboard.server.common.data.msg.TbMsgType.TIMESERIES_UPDATED;
@Slf4j @Slf4j
@Deprecated
class DeviceState { class DeviceState {
private final boolean persistState; 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; package org.thingsboard.rule.engine.profile;
@Deprecated
public interface DynamicPredicateValueCtx { public interface DynamicPredicateValueCtx {
EntityKeyValue getTenantValue(String key); 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; import java.util.concurrent.ExecutionException;
@Slf4j @Slf4j
@Deprecated
public class DynamicPredicateValueCtxImpl implements DynamicPredicateValueCtx { public class DynamicPredicateValueCtxImpl implements DynamicPredicateValueCtx {
private final TenantId tenantId; private final TenantId tenantId;
private CustomerId customerId; 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; import org.thingsboard.server.common.data.kv.DataType;
@EqualsAndHashCode @EqualsAndHashCode
@Deprecated
class EntityKeyValue { class EntityKeyValue {
@Getter @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; import java.util.concurrent.CopyOnWriteArrayList;
@Deprecated
class ProfileState { class ProfileState {
private DeviceProfile deviceProfile; 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; import java.util.Set;
@Deprecated
class SnapshotUpdate { class SnapshotUpdate {
@Getter @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 @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.ACTION,
name = "device profile", name = "device profile (deprecated)", // TODO: add description on why is it deprecated and what to use
customRelations = true, customRelations = true,
relationTypes = {"Alarm Created", "Alarm Updated", "Alarm Severity Updated", "Alarm Cleared", "Success", "Failure"}, relationTypes = {"Alarm Created", "Alarm Updated", "Alarm Severity Updated", "Alarm Cleared", "Success", "Failure"},
version = 1, version = 1,
configClazz = TbDeviceProfileNodeConfiguration.class, 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 " + 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.", "'Alarm Created', 'Alarm Updated', 'Alarm Severity Updated' and 'Alarm Cleared' or simply 'Success' if no alarms were affected.",
configDirective = "tbActionNodeDeviceProfileConfig", configDirective = "tbActionNodeDeviceProfileConfig",
docUrl = "https://thingsboard.io/docs/user-guide/rule-engine-2-0/nodes/action/device-profile/" docUrl = "https://thingsboard.io/docs/user-guide/rule-engine-2-0/nodes/action/device-profile/"
) )
@Deprecated
public class TbDeviceProfileNode implements TbNode { public class TbDeviceProfileNode implements TbNode {
private TbDeviceProfileNodeConfiguration config; 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 @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated
public class TbDeviceProfileNodeConfiguration implements NodeConfiguration<TbDeviceProfileNodeConfiguration> { public class TbDeviceProfileNodeConfiguration implements NodeConfiguration<TbDeviceProfileNodeConfiguration> {
private boolean persistAlarmRulesState; 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 @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Deprecated
public class PersistedAlarmRuleState { public class PersistedAlarmRuleState {
private long lastEventTs; 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; import java.util.Map;
@Data @Data
@Deprecated
public class PersistedAlarmState { public class PersistedAlarmState {
private Map<AlarmSeverity, PersistedAlarmRuleState> createRuleStates; 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; import java.util.Map;
@Data @Data
@Deprecated
public class PersistedDeviceState { public class PersistedDeviceState {
Map<String, PersistedAlarmState> alarmStates; Map<String, PersistedAlarmState> alarmStates;

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

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

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

@ -60,7 +60,7 @@
#entityAutocomplete="matAutocomplete" #entityAutocomplete="matAutocomplete"
[displayWith]="displayEntityFn"> [displayWith]="displayEntityFn">
<mat-option *ngFor="let entity of filteredEntities | async" [value]="entity"> <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>
<mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null"> <mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null">
<div (click)="$event.stopPropagation()"> <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 { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state'; import { AppState } from '@app/core/core.state';
import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; 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 { EntityId } from '@shared/models/id/entity-id';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { getCurrentAuthUser } from '@core/auth/auth.selectors';
@ -138,6 +138,10 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
@coerceArray() @coerceArray()
additionalClasses: Array<string>; additionalClasses: Array<string>;
@Input()
@coerceBoolean()
useEntityDisplayName = false;
@Output() @Output()
entityChanged = new EventEmitter<BaseData<EntityId>>(); entityChanged = new EventEmitter<BaseData<EntityId>>();
@ -395,7 +399,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
} }
displayEntityFn(entity?: BaseData<EntityId>): string | undefined { 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>>> { 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" *ngIf="modelValue.entityType"
[required]="required" [required]="required"
[entityType]="modelValue.entityType" [entityType]="modelValue.entityType"
[useEntityDisplayName]="useEntityDisplayName"
formControlName="entityIds"> formControlName="entityIds">
</tb-entity-list> </tb-entity-list>
</div> </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() @Input()
additionEntityTypes: {[key in string]: string} = {}; additionEntityTypes: {[key in string]: string} = {};
@Input({transform: booleanAttribute})
useEntityDisplayName = false;
displayEntityTypeSelect: boolean; displayEntityTypeSelect: boolean;
private defaultEntityType: EntityType | AliasEntityType = null; 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" class="tb-chip-row-ellipsis"
[removable]="!disabled" [removable]="!disabled"
(removed)="remove(entity)"> (removed)="remove(entity)">
{{entity.name}} {{ displayEntityFn(entity) }}
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon> <mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
</mat-chip-row> </mat-chip-row>
<input matInput type="text" placeholder="{{ !disabled ? placeholderText : '' }}" <input matInput type="text" placeholder="{{ !disabled ? placeholderText : '' }}"
@ -51,7 +51,7 @@
class="tb-autocomplete" class="tb-autocomplete"
[displayWith]="displayEntityFn"> [displayWith]="displayEntityFn">
<mat-option *ngFor="let entity of filteredEntities | async" [value]="entity"> <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>
<mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null"> <mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null">
<div (click)="$event.stopPropagation()"> <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 { filter, map, mergeMap, share, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { EntityType } from '@shared/models/entity-type.models'; 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 { EntityId } from '@shared/models/id/entity-id';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { MatAutocomplete } from '@angular/material/autocomplete'; import { MatAutocomplete } from '@angular/material/autocomplete';
@ -125,6 +125,10 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, OnChan
@coerceBoolean() @coerceBoolean()
allowCreateNew: boolean; allowCreateNew: boolean;
@Input()
@coerceBoolean()
useEntityDisplayName = false;
@Output() @Output()
createNew = new EventEmitter<string>(); createNew = new EventEmitter<string>();
@ -277,7 +281,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, OnChan
} }
public displayEntityFn(entity?: BaseData<EntityId>): string | undefined { 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>>> { 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" [appearance]="appearance"
[required]="required" [required]="required"
[entityType]="modelValue.entityType" [entityType]="modelValue.entityType"
[useEntityDisplayName]="useEntityDisplayName"
formControlName="entityId"> formControlName="entityId">
</tb-entity-autocomplete> </tb-entity-autocomplete>
</div> </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() @Input()
appearance: MatFormFieldAppearance = 'fill'; appearance: MatFormFieldAppearance = 'fill';
@Input()
@coerceBoolean()
useEntityDisplayName = false;
displayEntityTypeSelect: boolean; displayEntityTypeSelect: boolean;
AliasEntityType = AliasEntityType; 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 { export interface AlarmInfo extends Alarm {
originatorName: string; originatorName: string;
originatorLabel: string; originatorLabel: string;
originatorDisplayName?: string;
assignee: AlarmAssignee; assignee: AlarmAssignee;
} }
@ -172,6 +173,7 @@ export const simulatedAlarm: AlarmInfo = {
clearTs: 0, clearTs: 0,
assignTs: 0, assignTs: 0,
originatorName: 'Simulated', originatorName: 'Simulated',
originatorDisplayName: 'Simulated',
originatorLabel: 'Simulated', originatorLabel: 'Simulated',
assignee: { assignee: {
firstName: '', firstName: '',
@ -242,6 +244,11 @@ export const alarmFields: {[fieldName: string]: AlarmField} = {
value: 'originatorName', value: 'originatorName',
name: 'alarm.originator' name: 'alarm.originator'
}, },
originatorDisplayName: {
keyName: 'originatorDisplayName',
value: 'originatorDisplayName',
name: 'alarm.originator'
},
originatorLabel: { originatorLabel: {
keyName: 'originatorLabel', keyName: 'originatorLabel',
value: '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 { EntityId } from '@shared/models/id/entity-id';
import { HasUUID } from '@shared/models/id/has-uuid'; 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; export declare type HasId = EntityId | HasUUID;
@ -49,3 +51,12 @@ export function hasIdEquals(id1: HasId, id2: HasId): boolean {
return id1 === id2; 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', name: 'entity-field.label',
value: 'label' value: 'label'
}, },
displayName: {
keyName: 'displayName',
name: 'entity-field.name',
value: 'name'
},
queueName: { queueName: {
keyName: 'queueName', keyName: 'queueName',
name: 'entity-field.queue-name', name: 'entity-field.queue-name',

Loading…
Cancel
Save