Browse Source

Merge branch 'develop/3.5' of github.com:AndriiLandiak/thingsboard into feature/x509-device-cert-impr

pull/7935/head
Andrii Landiak 4 years ago
parent
commit
e7fc00e53d
  1. 77
      application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
  2. 2
      application/src/main/data/json/system/widget_bundles/alarm_widgets.json
  3. 16
      application/src/main/data/json/system/widget_bundles/maps.json
  4. 17
      application/src/main/data/upgrade/3.4.4/schema_update.sql
  5. 4
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  6. 5
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java
  7. 6
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  8. 13
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  9. 15
      application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
  10. 47
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  11. 2
      application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
  12. 17
      application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java
  13. 45
      application/src/main/java/org/thingsboard/server/controller/UsageInfoController.java
  14. 103
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  15. 2
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  16. 7
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  17. 42
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  18. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  19. 23
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  20. 70
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
  21. 28
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java
  22. 106
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
  23. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  24. 202
      application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java
  25. 42
      application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java
  26. 23
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  27. 23
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  28. 44
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  29. 121
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  30. 13
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java
  31. 114
      application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
  32. 19
      application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
  33. 5
      application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java
  34. 10
      application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java
  35. 6
      application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java
  36. 78
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java
  37. 10
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
  38. 10
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
  39. 8
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
  40. 59
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ApiUsageLimitTriggerProcessor.java
  41. 26
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java
  42. 6
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java
  43. 16
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java
  44. 10
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java
  45. 6
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java
  46. 15
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java
  47. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineMsgNotificationRuleTriggerProcessor.java
  48. 6
      application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
  49. 3
      application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java
  50. 10
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  51. 19
      application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java
  52. 35
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  53. 34
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  54. 67
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java
  55. 3
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java
  56. 34
      application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java
  57. 14
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  58. 27
      application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java
  59. 26
      application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
  60. 26
      application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java
  61. 10
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java
  62. 6
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java
  63. 34
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java
  64. 10
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java
  65. 53
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUpdate.java
  66. 1
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java
  67. 6
      application/src/main/resources/thingsboard.yml
  68. 149
      application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
  69. 137
      application/src/test/java/org/thingsboard/server/controller/BaseHomePageApiTest.java
  70. 188
      application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
  71. 73
      application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java
  72. 12
      application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
  73. 23
      application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java
  74. 54
      application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java
  75. 4
      application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
  76. 6
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  77. 78
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  78. 4
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  79. 3
      common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java
  80. 14
      common/cluster-api/src/main/proto/queue.proto
  81. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
  82. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  83. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  84. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  85. 26
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityCountService.java
  86. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java
  87. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java
  88. 11
      common/dao-api/src/main/java/org/thingsboard/server/dao/usage/UsageInfoService.java
  89. 9
      common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java
  90. 2
      common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java
  91. 21
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java
  92. 48
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordState.java
  93. 2
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  94. 18
      common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java
  95. 22
      common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java
  96. 43
      common/data/src/main/java/org/thingsboard/server/common/data/UsageInfo.java
  97. 20
      common/data/src/main/java/org/thingsboard/server/common/data/User.java
  98. 8
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmApiCallResult.java
  99. 8
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java
  100. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestPreview.java

77
application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json → application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json

@ -6,7 +6,8 @@
"firstRuleNodeId": null, "firstRuleNodeId": null,
"root": true, "root": true,
"debugMode": false, "debugMode": false,
"configuration": null "configuration": null,
"externalId": null
}, },
"metadata": { "metadata": {
"firstNodeIndex": 0, "firstNodeIndex": 0,
@ -23,7 +24,8 @@
"configuration": { "configuration": {
"persistAlarmRulesState": false, "persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false "fetchAlarmRulesStateOnStart": false
} },
"externalId": null
}, },
{ {
"additionalInfo": { "additionalInfo": {
@ -35,7 +37,8 @@
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"defaultTTL": 0 "defaultTTL": 0
} },
"externalId": null
}, },
{ {
"additionalInfo": { "additionalInfo": {
@ -46,8 +49,10 @@
"name": "Save Client Attributes", "name": "Save Client Attributes",
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"scope": "CLIENT_SCOPE" "scope": "CLIENT_SCOPE",
} "notifyDevice": "false"
},
"externalId": null
}, },
{ {
"additionalInfo": { "additionalInfo": {
@ -59,7 +64,8 @@
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"version": 0 "version": 0
} },
"externalId": null
}, },
{ {
"additionalInfo": { "additionalInfo": {
@ -73,7 +79,8 @@
"scriptLang": "TBEL", "scriptLang": "TBEL",
"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": {
@ -87,7 +94,8 @@
"scriptLang": "TBEL", "scriptLang": "TBEL",
"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": {
@ -99,19 +107,34 @@
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"timeoutInSeconds": 60 "timeoutInSeconds": 60
} },
"externalId": null
}, },
{ {
"additionalInfo": { "additionalInfo": {
"layoutX": 1129, "layoutX": 1126,
"layoutY": 52 "layoutY": 104
}, },
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode", "type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud", "name": "Push to cloud",
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"scope": "SERVER_SCOPE" "scope": "SERVER_SCOPE"
} },
"externalId": null
},
{
"additionalInfo": {
"layoutX": 826,
"layoutY": 601
},
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud",
"debugMode": false,
"configuration": {
"scope": "SERVER_SCOPE"
},
"externalId": null
} }
], ],
"connections": [ "connections": [
@ -132,24 +155,14 @@
}, },
{ {
"fromIndex": 3, "fromIndex": 3,
"toIndex": 6, "toIndex": 1,
"type": "RPC Request to Device" "type": "Post telemetry"
},
{
"fromIndex": 3,
"toIndex": 5,
"type": "Other"
}, },
{ {
"fromIndex": 3, "fromIndex": 3,
"toIndex": 2, "toIndex": 2,
"type": "Post attributes" "type": "Post attributes"
}, },
{
"fromIndex": 3,
"toIndex": 1,
"type": "Post telemetry"
},
{ {
"fromIndex": 3, "fromIndex": 3,
"toIndex": 4, "toIndex": 4,
@ -157,23 +170,23 @@
}, },
{ {
"fromIndex": 3, "fromIndex": 3,
"toIndex": 7, "toIndex": 5,
"type": "Attributes Updated" "type": "Other"
}, },
{ {
"fromIndex": 3, "fromIndex": 3,
"toIndex": 7, "toIndex": 6,
"type": "Attributes Deleted" "type": "RPC Request to Device"
}, },
{ {
"fromIndex": 3, "fromIndex": 3,
"toIndex": 7, "toIndex": 8,
"type": "Timeseries Deleted" "type": "Attributes Deleted"
}, },
{ {
"fromIndex": 3, "fromIndex": 3,
"toIndex": 7, "toIndex": 8,
"type": "Timeseries Updated" "type": "Attributes Updated"
} }
], ],
"ruleChainConnections": null "ruleChainConnections": null

2
application/src/main/data/json/system/widget_bundles/alarm_widgets.json

@ -23,7 +23,7 @@
"dataKeySettingsSchema": "", "dataKeySettingsSchema": "",
"settingsDirective": "tb-alarms-table-widget-settings", "settingsDirective": "tb-alarms-table-widget-settings",
"dataKeySettingsDirective": "tb-alarms-table-key-settings", "dataKeySettingsDirective": "tb-alarms-table-key-settings",
"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\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"allowAssign\":true,\"displayComments\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418},{\"name\":\"assignee\",\"type\":\"alarm\",\"label\":\"Assignee\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.5008441077416634}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false}" "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\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"allowAssign\":true,\"displayActivity\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418},{\"name\":\"assignee\",\"type\":\"alarm\",\"label\":\"Assignee\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.5008441077416634}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false}"
} }
} }
] ]

16
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

17
application/src/main/data/upgrade/3.4.4/schema_update.sql

@ -167,11 +167,12 @@ CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notific
ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255); ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255);
CREATE TABLE IF NOT EXISTS user_settings ( CREATE TABLE IF NOT EXISTS user_settings (
user_id uuid NOT NULL CONSTRAINT user_settings_pkey PRIMARY KEY, user_id uuid NOT NULL,
settings varchar(100000), type VARCHAR(50) NOT NULL,
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE settings varchar(10000),
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE,
CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, type)
); );
-- ALARM INFO VIEW -- ALARM INFO VIEW
DROP VIEW IF EXISTS alarm_info CASCADE; DROP VIEW IF EXISTS alarm_info CASCADE;
@ -193,15 +194,15 @@ COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id
WHEN a.originator_type = 18 THEN (select name from edge where id = a.originator_id) END WHEN a.originator_type = 18 THEN (select name from edge where id = a.originator_id) END
, 'Deleted') originator_name, , 'Deleted') originator_name,
COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id) COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id)
WHEN a.originator_type = 1 THEN (select COALESCE(title, email) from customer where id = a.originator_id) WHEN a.originator_type = 1 THEN (select COALESCE(NULLIF(title, ''), email) from customer where id = a.originator_id)
WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id) WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id) WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
WHEN a.originator_type = 4 THEN (select COALESCE(label, name) from asset where id = a.originator_id) WHEN a.originator_type = 4 THEN (select COALESCE(NULLIF(label, ''), name) from asset where id = a.originator_id)
WHEN a.originator_type = 5 THEN (select COALESCE(label, name) from device where id = a.originator_id) WHEN a.originator_type = 5 THEN (select COALESCE(NULLIF(label, ''), name) from device where id = a.originator_id)
WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id) WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id) WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id)
WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id) WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id)
WHEN a.originator_type = 18 THEN (select COALESCE(label, name) from edge where id = a.originator_id) END WHEN a.originator_type = 18 THEN (select COALESCE(NULLIF(label, ''), name) from edge where id = a.originator_id) END
, 'Deleted') as originator_label, , 'Deleted') as originator_label,
u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email
FROM alarm a FROM alarm a

4
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -71,7 +71,6 @@ import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
@ -90,6 +89,7 @@ import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.ComponentDiscoveryService;
@ -341,7 +341,7 @@ public class ActorSystemContext {
@Autowired @Autowired
@Getter @Getter
private NotificationRuleProcessingService notificationRuleProcessingService; private NotificationRuleProcessor notificationRuleProcessor;
@Autowired @Autowired
@Getter @Getter

5
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java

@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbActorStopReason; import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.dao.notification.trigger.RuleEngineComponentLifecycleEventTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineComponentLifecycleEventTrigger;
public abstract class RuleEngineComponentActor<T extends EntityId, P extends ComponentMsgProcessor<T>> extends ComponentActor<T, P> { public abstract class RuleEngineComponentActor<T extends EntityId, P extends ComponentMsgProcessor<T>> extends ComponentActor<T, P> {
@ -50,7 +50,8 @@ public abstract class RuleEngineComponentActor<T extends EntityId, P extends Com
} }
private void processNotificationRule(ComponentLifecycleEvent event, Throwable e) { private void processNotificationRule(ComponentLifecycleEvent event, Throwable e) {
systemContext.getNotificationRuleProcessingService().process(tenantId, RuleEngineComponentLifecycleEventTrigger.builder() systemContext.getNotificationRuleProcessor().process(RuleEngineComponentLifecycleEventTrigger.builder()
.tenantId(tenantId)
.ruleChainId(getRuleChainId()) .ruleChainId(getRuleChainId())
.ruleChainName(getRuleChainName()) .ruleChainName(getRuleChainName())
.componentId(id) .componentId(id)

6
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -49,6 +49,7 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg; import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.edge.EdgeSessionMsg; import org.thingsboard.server.common.msg.edge.EdgeSessionMsg;
import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
@ -209,7 +210,10 @@ public class TenantActor extends RuleChainManagerActor {
log.trace("[{}] Ack message because Rule Engine is disabled", tenantId); log.trace("[{}] Ack message because Rule Engine is disabled", tenantId);
tbMsg.getCallback().onSuccess(); tbMsg.getCallback().onSuccess();
} }
systemContext.getNotificationRuleProcessingService().process(tenantId, tbMsg); systemContext.getNotificationRuleProcessor().process(RuleEngineMsgTrigger.builder()
.tenantId(tenantId)
.msg(tbMsg)
.build());
} }
private void onRuleChainMsg(RuleChainAwareMsg msg) { private void onRuleChainMsg(RuleChainAwareMsg msg) {

13
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -103,6 +103,7 @@ import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.settings.UserDashboardAction;
import org.thingsboard.server.common.data.util.ThrowingBiFunction; import org.thingsboard.server.common.data.util.ThrowingBiFunction;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
@ -146,6 +147,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.instructions.EdgeInstallService; import org.thingsboard.server.service.edge.instructions.EdgeInstallService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService; import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
import org.thingsboard.server.service.entitiy.user.TbUserSettingsService;
import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
@ -168,6 +170,7 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.StringUtils.isNotEmpty; import static org.thingsboard.server.common.data.StringUtils.isNotEmpty;
@ -202,7 +205,7 @@ public abstract class BaseController {
protected UserService userService; protected UserService userService;
@Autowired @Autowired
protected UserSettingsService userSettingsService; protected TbUserSettingsService userSettingsService;
@Autowired @Autowired
protected DeviceService deviceService; protected DeviceService deviceService;
@ -445,6 +448,14 @@ public abstract class BaseController {
} }
} }
protected <T> T checkEnumParameter(String name, String param, Function<String, T> valueOf) throws ThingsboardException {
try {
return valueOf.apply(param.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ThingsboardException(name + " \"" + param + "\" is not supported!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
}
UUID toUUID(String id) throws ThingsboardException { UUID toUUID(String id) throws ThingsboardException {
try { try {
return UUID.fromString(id); return UUID.fromString(id);

15
application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmCountQuery;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityCountQuery;
@ -93,6 +94,20 @@ public class EntityQueryController extends BaseController {
return this.entityQueryService.findAlarmDataByQuery(getCurrentUser(), query); return this.entityQueryService.findAlarmDataByQuery(getCurrentUser(), query);
} }
@ApiOperation(value = "Count Alarms by Query (countAlarmsByQuery)", notes = "Returns the number of alarms that match the query definition.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarmsQuery/count", method = RequestMethod.POST)
@ResponseBody
public long countAlarmsByQuery(@ApiParam(value = "A JSON value representing the alarm count query.")
@RequestBody AlarmCountQuery query) throws ThingsboardException {
checkNotNull(query);
UserId assigneeId = query.getAssigneeId();
if (assigneeId != null) {
checkUserId(assigneeId, Operation.READ);
}
return this.entityQueryService.countAlarmsByQuery(getCurrentUser(), query);
}
@ApiOperation(value = "Find Entity Keys by Query", @ApiOperation(value = "Find Entity Keys by Query",
notes = "Uses entity data query (see 'Find Entity Data by Query') to find first 100 entities. Then fetch and return all unique time-series and/or attribute keys. Used mostly for UI hints.") notes = "Uses entity data query (see 'Find Entity Data by Query') to find first 100 entities. Then fetch and return all unique time-series and/or attribute keys. Used mostly for UI hints.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")

47
application/src/main/java/org/thingsboard/server/controller/NotificationController.java

@ -42,10 +42,12 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestInfo; import org.thingsboard.server.common.data.notification.NotificationRequestInfo;
import org.thingsboard.server.common.data.notification.NotificationRequestPreview; import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
import org.thingsboard.server.common.data.notification.info.UserOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType; import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -62,7 +64,6 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
@ -187,14 +188,12 @@ public class NotificationController extends BaseController {
checkEntity(notificationRequest.getId(), notificationRequest, NOTIFICATION); checkEntity(notificationRequest.getId(), notificationRequest, NOTIFICATION);
notificationRequest.setOriginatorEntityId(user.getId()); notificationRequest.setOriginatorEntityId(user.getId());
if (notificationRequest.getInfo() != null && !(notificationRequest.getInfo() instanceof UserOriginatedNotificationInfo)) { notificationRequest.setInfo(null);
throw new IllegalArgumentException("Unsupported notification info type");
}
notificationRequest.setRuleId(null); notificationRequest.setRuleId(null);
notificationRequest.setStatus(null); notificationRequest.setStatus(null);
notificationRequest.setStats(null); notificationRequest.setStats(null);
return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationCenter::processNotificationRequest); return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, (tenantId, request) -> notificationCenter.processNotificationRequest(tenantId, request, null));
} }
@PostMapping("/notification/request/preview") @PostMapping("/notification/request/preview")
@ -217,45 +216,49 @@ public class NotificationController extends BaseController {
NotificationProcessingContext tmpProcessingCtx = NotificationProcessingContext.builder() NotificationProcessingContext tmpProcessingCtx = NotificationProcessingContext.builder()
.tenantId(user.getTenantId()) .tenantId(user.getTenantId())
.request(request) .request(request)
.settings(null)
.template(template) .template(template)
.settings(null)
.build(); .build();
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = tmpProcessingCtx.getDeliveryMethods().stream() Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = tmpProcessingCtx.getDeliveryMethods().stream()
.collect(Collectors.toMap(m -> m, deliveryMethod -> { .collect(Collectors.toMap(m -> m, deliveryMethod -> {
Map<String, String> templateContext; NotificationRecipient recipient = null;
if (NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods().contains(deliveryMethod)) { if (NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods().contains(deliveryMethod)) {
templateContext = tmpProcessingCtx.createTemplateContext(user); recipient = userService.findUserById(user.getTenantId(), user.getId());
} else {
templateContext = Collections.emptyMap();
} }
return tmpProcessingCtx.getProcessedTemplate(deliveryMethod, templateContext); return tmpProcessingCtx.getProcessedTemplate(deliveryMethod, recipient);
})); }));
preview.setProcessedTemplates(processedTemplates); preview.setProcessedTemplates(processedTemplates);
// generic permission // generic permission
Set<User> recipientsPreview = new LinkedHashSet<>(); Set<String> recipientsPreview = new LinkedHashSet<>();
Map<String, Integer> recipientsCountByTarget = new HashMap<>(); Map<String, Integer> recipientsCountByTarget = new HashMap<>();
List<NotificationTarget> targets = notificationTargetService.findNotificationTargetsByTenantIdAndIds(user.getTenantId(), List<NotificationTarget> targets = notificationTargetService.findNotificationTargetsByTenantIdAndIds(user.getTenantId(),
request.getTargets().stream().map(NotificationTargetId::new).collect(Collectors.toList())); request.getTargets().stream().map(NotificationTargetId::new).collect(Collectors.toList()));
for (NotificationTarget target : targets) { for (NotificationTarget target : targets) {
int recipientsCount; int recipientsCount;
List<NotificationRecipient> recipientsPart;
if (target.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) { if (target.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) {
PageData<User> recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, PageData<User> recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(),
target.getConfiguration(), new PageLink(recipientsPreviewSize)); (PlatformUsersNotificationTargetConfig) target.getConfiguration(), new PageLink(recipientsPreviewSize));
recipientsCount = (int) recipients.getTotalElements(); recipientsCount = (int) recipients.getTotalElements();
for (User recipient : recipients.getData()) { recipientsPart = recipients.getData().stream().map(r -> (NotificationRecipient) r).collect(Collectors.toList());
if (recipientsPreview.size() < recipientsPreviewSize) {
recipientsPreview.add(recipient);
} else {
break;
}
}
} else { } else {
recipientsCount = 1; recipientsCount = 1;
recipientsPart = List.of(((SlackNotificationTargetConfig) target.getConfiguration()).getConversation());
}
for (NotificationRecipient recipient : recipientsPart) {
if (recipientsPreview.size() < recipientsPreviewSize) {
recipientsPreview.add(recipient.getTitle());
} else {
break;
}
} }
recipientsCountByTarget.put(target.getName(), recipientsCount); recipientsCountByTarget.put(target.getName(), recipientsCount);
} }
preview.setRecipientsPreview(recipientsPreview); preview.setRecipientsPreview(recipientsPreview);
preview.setRecipientsCountByTarget(recipientsCountByTarget); preview.setRecipientsCountByTarget(recipientsCountByTarget);
preview.setTotalRecipientsCount(recipientsCountByTarget.values().stream().mapToInt(Integer::intValue).sum()); preview.setTotalRecipientsCount(recipientsCountByTarget.values().stream().mapToInt(Integer::intValue).sum());

2
application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java

@ -116,7 +116,7 @@ public class NotificationTargetController extends BaseController {
} }
PageLink pageLink = createPageLink(pageSize, page, null, null, null); PageLink pageLink = createPageLink(pageSize, page, null, null, null);
return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, notificationTarget.getConfiguration(), pageLink); return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), (PlatformUsersNotificationTargetConfig) notificationTarget.getConfiguration(), pageLink);
} }
@GetMapping(value = "/targets", params = {"ids"}) @GetMapping(value = "/targets", params = {"ids"})

17
application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java

@ -17,6 +17,7 @@ package org.thingsboard.server.controller;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
@ -136,16 +137,20 @@ public class NotificationTemplateController extends BaseController {
@GetMapping("/slack/conversations") @GetMapping("/slack/conversations")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List<SlackConversation> listSlackConversations(@RequestParam SlackConversationType type, public List<SlackConversation> listSlackConversations(@RequestParam SlackConversationType type,
@RequestParam(required = false) String token,
@AuthenticationPrincipal SecurityUser user) { @AuthenticationPrincipal SecurityUser user) {
// generic permission // generic permission
NotificationSettings settings = notificationSettingsService.findNotificationSettings(user.getTenantId()); if (StringUtils.isEmpty(token)) {
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig) NotificationSettings settings = notificationSettingsService.findNotificationSettings(user.getTenantId());
settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK); SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
if (slackConfig == null) { settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK);
throw new IllegalArgumentException("Slack is not configured"); if (slackConfig == null) {
throw new IllegalArgumentException("Slack is not configured");
}
token = slackConfig.getBotToken();
} }
return slackService.listConversations(user.getTenantId(), slackConfig.getBotToken(), type); return slackService.listConversations(user.getTenantId(), token, type);
} }
} }

45
application/src/main/java/org/thingsboard/server/controller/UsageInfoController.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.UsageInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.dao.usage.UsageInfoService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@RestController
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
public class UsageInfoController extends BaseController {
@Autowired
private UsageInfoService usageInfoService;
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/usage", method = RequestMethod.GET)
@ResponseBody
public UsageInfo getTenantUsageInfo() throws ThingsboardException {
return checkNotNull(usageInfoService.getUsageInfo(getCurrentUser().getTenantId()));
}
}

103
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -38,12 +38,14 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.UserEmailInfo; import org.thingsboard.server.common.data.UserEmailInfo;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -55,9 +57,13 @@ import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.UserSettings; import org.thingsboard.server.common.data.settings.LastVisitedDashboardInfo;
import org.thingsboard.server.common.data.settings.UserDashboardAction;
import org.thingsboard.server.common.data.settings.UserDashboardsInfo;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent;
import org.thingsboard.server.common.data.security.model.JwtPair; import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.user.TbUserService; import org.thingsboard.server.service.entitiy.user.TbUserService;
import org.thingsboard.server.service.query.EntityQueryService; import org.thingsboard.server.service.query.EntityQueryService;
@ -76,6 +82,7 @@ import java.util.Map;
import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD; import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.DEFAULT_DASHBOARD; import static org.thingsboard.server.controller.ControllerConstants.DEFAULT_DASHBOARD;
import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOARD; import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOARD;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
@ -438,13 +445,14 @@ public class UserController extends BaseController {
} }
@ApiOperation(value = "Save user settings (saveUserSettings)", @ApiOperation(value = "Save user settings (saveUserSettings)",
notes = "Save user settings represented in json format for authorized user. " ) notes = "Save user settings represented in json format for authorized user. ")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@PostMapping(value = "/user/settings") @PostMapping(value = "/user/settings")
public JsonNode saveUserSettings(@RequestBody JsonNode settings) throws ThingsboardException { public JsonNode saveUserSettings(@RequestBody JsonNode settings) throws ThingsboardException {
SecurityUser currentUser = getCurrentUser(); SecurityUser currentUser = getCurrentUser();
UserSettings userSettings = new UserSettings(); UserSettings userSettings = new UserSettings();
userSettings.setType(UserSettingsType.GENERAL);
userSettings.setSettings(settings); userSettings.setSettings(settings);
userSettings.setUserId(currentUser.getId()); userSettings.setUserId(currentUser.getId());
return userSettingsService.saveUserSettings(currentUser.getTenantId(), userSettings).getSettings(); return userSettingsService.saveUserSettings(currentUser.getTenantId(), userSettings).getSettings();
@ -458,31 +466,108 @@ public class UserController extends BaseController {
@PutMapping(value = "/user/settings") @PutMapping(value = "/user/settings")
public void putUserSettings(@RequestBody JsonNode settings) throws ThingsboardException { public void putUserSettings(@RequestBody JsonNode settings) throws ThingsboardException {
SecurityUser currentUser = getCurrentUser(); SecurityUser currentUser = getCurrentUser();
userSettingsService.updateUserSettings(currentUser.getTenantId(), currentUser.getId(), settings); userSettingsService.updateUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL, settings);
} }
@ApiOperation(value = "Get user settings (getUserSettings)", @ApiOperation(value = "Get user settings (getUserSettings)",
notes = "Fetch the User settings based on authorized user. " ) notes = "Fetch the User settings based on authorized user. ")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/user/settings") @GetMapping(value = "/user/settings")
public JsonNode getUserSettings() throws ThingsboardException { public JsonNode getUserSettings() throws ThingsboardException {
SecurityUser currentUser = getCurrentUser(); SecurityUser currentUser = getCurrentUser();
UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId()); UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL);
return userSettings == null ? JacksonUtil.newObjectNode(): userSettings.getSettings(); return userSettings == null ? JacksonUtil.newObjectNode() : userSettings.getSettings();
} }
@ApiOperation(value = "Delete user settings (deleteUserSettings)", @ApiOperation(value = "Delete user settings (deleteUserSettings)",
notes = "Delete user settings by specifying list of json element xpaths. \n " + notes = "Delete user settings by specifying list of json element xpaths. \n " +
"Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter" ) "Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/user/settings/{paths}", method = RequestMethod.DELETE) @RequestMapping(value = "/user/settings/{paths}", method = RequestMethod.DELETE)
public void deleteUserSettings(@ApiParam(value = PATHS) public void deleteUserSettings(@ApiParam(value = PATHS)
@PathVariable(PATHS) String paths) throws ThingsboardException { @PathVariable(PATHS) String paths) throws ThingsboardException {
checkParameter(USER_ID, paths); checkParameter(USER_ID, paths);
SecurityUser currentUser = getCurrentUser(); SecurityUser currentUser = getCurrentUser();
userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), Arrays.asList(paths.split(","))); userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL, Arrays.asList(paths.split(",")));
}
@ApiOperation(value = "Update user settings (saveUserSettings)",
notes = "Update user settings for authorized user. Only specified json elements will be updated." +
"Example: you have such settings: {A:5, B:{C:10, D:20}}. Updating it with {B:{C:10, D:30}} will result in" +
"{A:5, B:{C:10, D:30}}. The same could be achieved by putting {B.D:30}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@PutMapping(value = "/user/settings/{type}")
public void putUserSettings(@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".")
@PathVariable("type") String strType, @RequestBody JsonNode settings) throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf);
checkNotReserved(strType, type);
userSettingsService.updateUserSettings(currentUser.getTenantId(), currentUser.getId(), type, settings);
}
@ApiOperation(value = "Get user settings (getUserSettings)",
notes = "Fetch the User settings based on authorized user. ")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/user/settings/{type}")
public JsonNode getUserSettings(@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".")
@PathVariable("type") String strType) throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf);
checkNotReserved(strType, type);
UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId(), type);
return userSettings == null ? JacksonUtil.newObjectNode() : userSettings.getSettings();
}
@ApiOperation(value = "Delete user settings (deleteUserSettings)",
notes = "Delete user settings by specifying list of json element xpaths. \n " +
"Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/user/settings/{type}/{paths}", method = RequestMethod.DELETE)
public void deleteUserSettings(@ApiParam(value = PATHS)
@PathVariable(PATHS) String paths,
@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".")
@PathVariable("type") String strType) throws ThingsboardException {
checkParameter(USER_ID, paths);
UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf);
checkNotReserved(strType, type);
SecurityUser currentUser = getCurrentUser();
userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), type, Arrays.asList(paths.split(",")));
}
@ApiOperation(value = "Get information about last visited and starred dashboards (getLastVisitedDashboards)",
notes = "Fetch the list of last visited and starred dashboards. Both lists are limited to 10 items." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/user/dashboards")
public UserDashboardsInfo getUserDashboardsInfo() throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
return userSettingsService.findUserDashboardsInfo(currentUser.getTenantId(), currentUser.getId());
}
@ApiOperation(value = "Report action of User over the dashboard (reportUserDashboardAction)",
notes = "Report action of User over the dashboard. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/user/dashboards/{dashboardId}/{action}", method = RequestMethod.GET)
@ResponseBody
public UserDashboardsInfo reportUserDashboardAction(
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DashboardController.DASHBOARD_ID) String strDashboardId,
@ApiParam(value = "Dashboard action, one of: \"visit\", \"star\" or \"unstar\".")
@PathVariable("action") String strAction) throws ThingsboardException {
checkParameter(DashboardController.DASHBOARD_ID, strDashboardId);
checkParameter("action", strAction);
UserDashboardAction action = checkEnumParameter("Action", strAction, UserDashboardAction::valueOf);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
checkDashboardInfoId(dashboardId, Operation.READ);
SecurityUser currentUser = getCurrentUser();
return userSettingsService.reportUserDashboardAction(currentUser.getTenantId(), currentUser.getId(), dashboardId, action);
}
private void checkNotReserved(String strType, UserSettingsType type) throws ThingsboardException {
if (type.isReserved()) {
throw new ThingsboardException("Settings with type: " + strType + " are reserved for internal use!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
} }
} }

2
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -295,6 +295,8 @@ public class ThingsboardInstallService {
systemDataLoaderService.loadSystemWidgets(); systemDataLoaderService.loadSystemWidgets();
systemDataLoaderService.createOAuth2Templates(); systemDataLoaderService.createOAuth2Templates();
systemDataLoaderService.createQueues(); systemDataLoaderService.createQueues();
systemDataLoaderService.createDefaultNotificationConfigs();
// systemDataLoaderService.loadSystemPlugins(); // systemDataLoaderService.loadSystemPlugins();
// systemDataLoaderService.loadSystemRules(); // systemDataLoaderService.loadSystemRules();
installScripts.loadSystemLwm2mResources(); installScripts.loadSystemLwm2mResources();

7
application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java

@ -142,6 +142,13 @@ public class EntityActionService {
if (user != null) { if (user != null) {
metaData.putValue("userId", user.getId().toString()); metaData.putValue("userId", user.getId().toString());
metaData.putValue("userName", user.getName()); metaData.putValue("userName", user.getName());
metaData.putValue("userEmail", user.getEmail());
if (user.getFirstName() != null) {
metaData.putValue("userFirstName", user.getFirstName());
}
if (user.getLastName() != null) {
metaData.putValue("userLastName", user.getLastName());
}
} }
if (customerId != null && !customerId.isNullUid()) { if (customerId != null && !customerId.isNullUid()) {
metaData.putValue("customerId", customerId.toString()); metaData.putValue("customerId", customerId.toString());

42
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -24,13 +24,12 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageRecordState;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.ApiUsageStateMailMessage;
import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.ApiUsageStateValue;
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.StringUtils;
@ -53,7 +52,8 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.SchedulerUtils; import org.thingsboard.server.common.msg.tools.SchedulerUtils;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.common.msg.notification.trigger.ApiUsageLimitTrigger;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -63,6 +63,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.partition.AbstractPartitionBasedService; import org.thingsboard.server.service.partition.AbstractPartitionBasedService;
import org.thingsboard.server.service.telemetry.InternalTelemetryService; import org.thingsboard.server.service.telemetry.InternalTelemetryService;
@ -79,8 +80,6 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -108,8 +107,9 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
private final ApiUsageStateService apiUsageStateService; private final ApiUsageStateService apiUsageStateService;
private final TbTenantProfileCache tenantProfileCache; private final TbTenantProfileCache tenantProfileCache;
private final MailService mailService; private final MailService mailService;
private final NotificationRuleProcessingService notificationRuleProcessingService; private final NotificationRuleProcessor notificationRuleProcessor;
private final DbCallbackExecutorService dbExecutor; private final DbCallbackExecutorService dbExecutor;
private final MailExecutorService mailExecutor;
@Lazy @Lazy
@Autowired @Autowired
@ -130,8 +130,6 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
private final Lock updateLock = new ReentrantLock(); private final Lock updateLock = new ReentrantLock();
private final ExecutorService mailExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("api-usage-svc-mail"));
@PostConstruct @PostConstruct
public void init() { public void init() {
super.init(); super.init();
@ -340,32 +338,35 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK);
if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) { if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) {
String email = tenantService.findTenantById(state.getTenantId()).getEmail(); String email = tenantService.findTenantById(state.getTenantId()).getEmail();
if (StringUtils.isNotEmpty(email)) { result.forEach((apiFeature, stateValue) -> {
result.forEach((apiFeature, stateValue) -> { ApiUsageRecordState recordState = createApiUsageRecordState((TenantApiUsageState) state, apiFeature, stateValue);
notificationRuleProcessor.process(ApiUsageLimitTrigger.builder()
.tenantId(state.getTenantId())
.state(recordState)
.status(stateValue)
.build());
if (StringUtils.isNotEmpty(email)) {
mailExecutor.submit(() -> { mailExecutor.submit(() -> {
try { try {
mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage((TenantApiUsageState) state, apiFeature, stateValue)); mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, recordState);
} catch (ThingsboardException e) { } catch (ThingsboardException e) {
log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e);
} }
}); });
}); }
} else { });
log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId());
}
} }
} }
private ApiUsageStateMailMessage createStateMailMessage(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) { private ApiUsageRecordState createApiUsageRecordState(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) {
StateChecker checker = getStateChecker(stateValue); StateChecker checker = getStateChecker(stateValue);
for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) { for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) {
long threshold = state.getProfileThreshold(apiUsageRecordKey); long threshold = state.getProfileThreshold(apiUsageRecordKey);
long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey); long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey);
long value = state.get(apiUsageRecordKey); long value = state.get(apiUsageRecordKey);
if (checker.check(threshold, warnThreshold, value)) { if (checker.check(threshold, warnThreshold, value)) {
return new ApiUsageStateMailMessage(apiUsageRecordKey, threshold, value); return new ApiUsageRecordState(apiFeature, apiUsageRecordKey, threshold, value);
} }
} }
return null; return null;
@ -377,7 +378,7 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
} else if (ApiUsageStateValue.WARNING.equals(stateValue)) { } else if (ApiUsageStateValue.WARNING.equals(stateValue)) {
return (t, wt, v) -> v < t && v >= wt; return (t, wt, v) -> v < t && v >= wt;
} else { } else {
return (t, wt, v) -> v >= t; return (t, wt, v) -> t > 0 && v >= t;
} }
} }
@ -529,8 +530,5 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
@PreDestroy @PreDestroy
private void destroy() { private void destroy() {
super.stop(); super.stop();
if (mailExecutor != null) {
mailExecutor.shutdownNow();
}
} }
} }

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -423,6 +423,7 @@ public final class EdgeGrpcSession implements Closeable {
stopCurrentSendDownlinkMsgsTask(null); stopCurrentSendDownlinkMsgsTask(null);
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Failed to send downlink msgs. Error msg {}", this.sessionId, e.getMessage(), e);
stopCurrentSendDownlinkMsgsTask(e); stopCurrentSendDownlinkMsgsTask(e);
} }
}; };
@ -681,7 +682,7 @@ public final class EdgeGrpcSession implements Closeable {
public void stopCurrentSendDownlinkMsgsTask(Exception e) { public void stopCurrentSendDownlinkMsgsTask(Exception e) {
if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) { if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) {
if (e != null) { if (e != null) {
log.warn(e.getMessage(), e); log.debug(e.getMessage());
sessionState.getSendDownlinkMsgsFuture().setException(e); sessionState.getSendDownlinkMsgsFuture().setException(e);
} else { } else {
sessionState.getSendDownlinkMsgsFuture().set(null); sessionState.getSendDownlinkMsgsFuture().set(null);

23
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -485,4 +485,27 @@ public abstract class BaseEdgeProcessor {
} }
return customerId; return customerId;
} }
protected boolean isEntityExists(TenantId tenantId, EntityId entityId) {
switch (entityId.getEntityType()) {
case TENANT:
return tenantService.findTenantById(tenantId) != null;
case DEVICE:
return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null;
case ASSET:
return assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null;
case ENTITY_VIEW:
return entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null;
case CUSTOMER:
return customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null;
case USER:
return userService.findUserById(tenantId, new UserId(entityId.getId())) != null;
case DASHBOARD:
return dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null;
case EDGE:
return edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())) != null;
default:
return false;
}
}
} }

70
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java

@ -19,19 +19,19 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import java.util.UUID; import java.util.UUID;
@ -43,51 +43,59 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor {
log.trace("[{}] processAlarmMsg [{}]", tenantId, alarmUpdateMsg); log.trace("[{}] processAlarmMsg [{}]", tenantId, alarmUpdateMsg);
EntityId originatorId = getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(), EntityId originatorId = getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(),
EntityType.valueOf(alarmUpdateMsg.getOriginatorType())); EntityType.valueOf(alarmUpdateMsg.getOriginatorType()));
AlarmId alarmId = new AlarmId(new UUID(alarmUpdateMsg.getIdMSB(), alarmUpdateMsg.getIdLSB()));
if (originatorId == null) { if (originatorId == null) {
log.warn("Originator not found for the alarm msg {}", alarmUpdateMsg); log.warn("Originator not found for the alarm msg {}", alarmUpdateMsg);
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
try { try {
Alarm existentAlarm = alarmService.findLatestActiveByOriginatorAndType(tenantId, originatorId, alarmUpdateMsg.getType());
switch (alarmUpdateMsg.getMsgType()) { switch (alarmUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE: case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE: case ENTITY_UPDATED_RPC_MESSAGE:
if (existentAlarm == null || existentAlarm.getStatus().isCleared()) { Alarm alarm = new Alarm();
existentAlarm = new Alarm(); alarm.setId(alarmId);
existentAlarm.setTenantId(tenantId); alarm.setTenantId(tenantId);
existentAlarm.setType(alarmUpdateMsg.getName()); alarm.setType(alarmUpdateMsg.getName());
existentAlarm.setOriginator(originatorId); alarm.setOriginator(originatorId);
existentAlarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity())); alarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity()));
existentAlarm.setStartTs(alarmUpdateMsg.getStartTs()); alarm.setStartTs(alarmUpdateMsg.getStartTs());
existentAlarm.setClearTs(alarmUpdateMsg.getClearTs());
existentAlarm.setPropagate(alarmUpdateMsg.getPropagate());
}
var alarmStatus = AlarmStatus.valueOf(alarmUpdateMsg.getStatus()); var alarmStatus = AlarmStatus.valueOf(alarmUpdateMsg.getStatus());
existentAlarm.setCleared(alarmStatus.isCleared()); alarm.setClearTs(alarmUpdateMsg.getClearTs());
existentAlarm.setAcknowledged(alarmStatus.isAck()); alarm.setPropagate(alarmUpdateMsg.getPropagate());
existentAlarm.setAckTs(alarmUpdateMsg.getAckTs()); alarm.setCleared(alarmStatus.isCleared());
existentAlarm.setEndTs(alarmUpdateMsg.getEndTs()); alarm.setAcknowledged(alarmStatus.isAck());
existentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); alarm.setAckTs(alarmUpdateMsg.getAckTs());
alarmService.createOrUpdateAlarm(existentAlarm); alarm.setEndTs(alarmUpdateMsg.getEndTs());
break; alarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()));
if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(alarmUpdateMsg.getMsgType())) {
alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, null, alarmId));
} else {
alarmService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm));
}
return Futures.immediateFuture(null);
case ALARM_ACK_RPC_MESSAGE: case ALARM_ACK_RPC_MESSAGE:
if (existentAlarm != null) { Alarm alarmToAck = alarmService.findAlarmById(tenantId, alarmId);
alarmService.acknowledgeAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs()); if (alarmToAck != null) {
alarmService.acknowledgeAlarm(tenantId, alarmId, alarmUpdateMsg.getAckTs());
} }
break; return Futures.immediateFuture(null);
case ALARM_CLEAR_RPC_MESSAGE: case ALARM_CLEAR_RPC_MESSAGE:
if (existentAlarm != null) { Alarm alarmToClear = alarmService.findAlarmById(tenantId, alarmId);
alarmService.clearAlarm(tenantId, existentAlarm.getId(), if (alarmToClear != null) {
alarmUpdateMsg.getAckTs(), JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); alarmService.clearAlarm(tenantId, alarmId, alarmUpdateMsg.getClearTs(),
JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()));
} }
break; return Futures.immediateFuture(null);
case ENTITY_DELETED_RPC_MESSAGE: case ENTITY_DELETED_RPC_MESSAGE:
if (existentAlarm != null) { Alarm alarmToDelete = alarmService.findAlarmById(tenantId, alarmId);
alarmService.delAlarm(tenantId, existentAlarm.getId()); if (alarmToDelete != null) {
alarmService.delAlarm(tenantId, alarmId);
} }
break; return Futures.immediateFuture(null);
case UNRECOGNIZED:
default:
return handleUnsupportedMsgType(alarmUpdateMsg.getMsgType());
} }
return Futures.immediateFuture(null);
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] Failed to process alarm update msg [{}]", tenantId, alarmUpdateMsg, e); log.error("[{}] Failed to process alarm update msg [{}]", tenantId, alarmUpdateMsg, e);
return Futures.immediateFailedFuture(e); return Futures.immediateFailedFuture(e);

28
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java

@ -20,16 +20,9 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
@ -80,25 +73,4 @@ public abstract class BaseRelationProcessor extends BaseEdgeProcessor {
return Futures.immediateFailedFuture(e); return Futures.immediateFailedFuture(e);
} }
} }
private boolean isEntityExists(TenantId tenantId, EntityId entityId) {
switch (entityId.getEntityType()) {
case DEVICE:
return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null;
case ASSET:
return assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null;
case ENTITY_VIEW:
return entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null;
case CUSTOMER:
return customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null;
case USER:
return userService.findUserById(tenantId, new UserId(entityId.getId())) != null;
case DASHBOARD:
return dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null;
case EDGE:
return edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())) != null;
default:
return false;
}
}
} }

106
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.telemetry;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -90,42 +91,46 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
log.trace("[{}] processTelemetryMsg [{}]", tenantId, entityData); log.trace("[{}] processTelemetryMsg [{}]", tenantId, entityData);
List<ListenableFuture<Void>> result = new ArrayList<>(); List<ListenableFuture<Void>> result = new ArrayList<>();
EntityId entityId = constructEntityId(entityData.getEntityType(), entityData.getEntityIdMSB(), entityData.getEntityIdLSB()); EntityId entityId = constructEntityId(entityData.getEntityType(), entityData.getEntityIdMSB(), entityData.getEntityIdLSB());
if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg() || entityData.hasAttributesUpdatedMsg()) && entityId != null) { if (entityId != null && isEntityExists(tenantId, entityId)) {
Pair<TbMsgMetaData, CustomerId> pair = getBaseMsgMetadataAndCustomerId(tenantId, entityId); if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg() || entityData.hasAttributesUpdatedMsg())) {
TbMsgMetaData metaData = pair.getKey(); Pair<TbMsgMetaData, CustomerId> pair = getBaseMsgMetadataAndCustomerId(tenantId, entityId);
CustomerId customerId = pair.getValue(); TbMsgMetaData metaData = pair.getKey();
metaData.putValue(DataConstants.MSG_SOURCE_KEY, getMsgSourceKey()); CustomerId customerId = pair.getValue();
if (entityData.hasPostAttributesMsg()) { metaData.putValue(DataConstants.MSG_SOURCE_KEY, getMsgSourceKey());
result.add(processPostAttributes(tenantId, customerId, entityId, entityData.getPostAttributesMsg(), metaData)); if (entityData.hasPostAttributesMsg()) {
} result.add(processPostAttributes(tenantId, customerId, entityId, entityData.getPostAttributesMsg(), metaData));
if (entityData.hasAttributesUpdatedMsg()) { }
metaData.putValue("scope", entityData.getPostAttributeScope()); if (entityData.hasAttributesUpdatedMsg()) {
result.add(processAttributesUpdate(tenantId, customerId, entityId, entityData.getAttributesUpdatedMsg(), metaData)); metaData.putValue("scope", entityData.getPostAttributeScope());
} result.add(processAttributesUpdate(tenantId, customerId, entityId, entityData.getAttributesUpdatedMsg(), metaData));
if (entityData.hasPostTelemetryMsg()) { }
result.add(processPostTelemetry(tenantId, customerId, entityId, entityData.getPostTelemetryMsg(), metaData)); if (entityData.hasPostTelemetryMsg()) {
} result.add(processPostTelemetry(tenantId, customerId, entityId, entityData.getPostTelemetryMsg(), metaData));
if (EntityType.DEVICE.equals(entityId.getEntityType())) { }
DeviceId deviceId = new DeviceId(entityId.getId()); if (EntityType.DEVICE.equals(entityId.getEntityType())) {
DeviceId deviceId = new DeviceId(entityId.getId());
long currentTs = System.currentTimeMillis(); long currentTs = System.currentTimeMillis();
TransportProtos.DeviceActivityProto deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() TransportProtos.DeviceActivityProto deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits()) .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) .setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) .setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
.setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits())
.setLastActivityTime(currentTs).build(); .setLastActivityTime(currentTs).build();
log.trace("[{}][{}] device activity time is going to be updated, ts {}", tenantId, deviceId, currentTs); log.trace("[{}][{}] device activity time is going to be updated, ts {}", tenantId, deviceId, currentTs);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(deviceId.getId(), tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(deviceId.getId(),
TransportProtos.ToCoreMsg.newBuilder().setDeviceActivityMsg(deviceActivityMsg).build()), null); TransportProtos.ToCoreMsg.newBuilder().setDeviceActivityMsg(deviceActivityMsg).build()), null);
}
} }
} if (entityData.hasAttributeDeleteMsg()) {
if (entityData.hasAttributeDeleteMsg()) { result.add(processAttributeDeleteMsg(tenantId, entityId, entityData.getAttributeDeleteMsg(), entityData.getEntityType()));
result.add(processAttributeDeleteMsg(tenantId, entityId, entityData.getAttributeDeleteMsg(), entityData.getEntityType())); }
} else {
log.warn("Skipping telemetry update msg because entity doesn't exists on edge, {}", entityData);
} }
return result; return result;
} }
@ -279,26 +284,31 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
private ListenableFuture<Void> processAttributeDeleteMsg(TenantId tenantId, EntityId entityId, AttributeDeleteMsg attributeDeleteMsg, private ListenableFuture<Void> processAttributeDeleteMsg(TenantId tenantId, EntityId entityId, AttributeDeleteMsg attributeDeleteMsg,
String entityType) { String entityType) {
SettableFuture<Void> futureToSet = SettableFuture.create();
String scope = attributeDeleteMsg.getScope(); String scope = attributeDeleteMsg.getScope();
List<String> attributeKeys = attributeDeleteMsg.getAttributeNamesList(); List<String> attributeKeys = attributeDeleteMsg.getAttributeNamesList();
attributesService.removeAll(tenantId, entityId, scope, attributeKeys); ListenableFuture<List<String>> removeAllFuture = attributesService.removeAll(tenantId, entityId, scope, attributeKeys);
if (EntityType.DEVICE.name().equals(entityType)) { return Futures.transformAsync(removeAllFuture, removeAttributes -> {
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete( if (EntityType.DEVICE.name().equals(entityType)) {
tenantId, (DeviceId) entityId, scope, attributeKeys), new TbQueueCallback() { SettableFuture<Void> futureToSet = SettableFuture.create();
@Override tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(
public void onSuccess(TbQueueMsgMetadata metadata) { tenantId, (DeviceId) entityId, scope, attributeKeys), new TbQueueCallback() {
futureToSet.set(null); @Override
} public void onSuccess(TbQueueMsgMetadata metadata) {
futureToSet.set(null);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("Can't process attribute delete msg [{}]", attributeDeleteMsg, t); log.error("Can't process attribute delete msg [{}]", attributeDeleteMsg, t);
futureToSet.setException(t); futureToSet.setException(t);
} }
}); });
} return futureToSet;
return futureToSet; } else {
return Futures.immediateFuture(null);
}
}, dbCallbackExecutorService);
} }
public EntityDataProto convertTelemetryEventToEntityDataProto(EntityType entityType, public EntityDataProto convertTelemetryEventToEntityDataProto(EntityType entityType,

2
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java

@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import java.util.List; import java.util.List;

202
application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java

@ -0,0 +1,202 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.entitiy.user;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.HasTitle;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.settings.AbstractUserDashboardInfo;
import org.thingsboard.server.common.data.settings.LastVisitedDashboardInfo;
import org.thingsboard.server.common.data.settings.StarredDashboardInfo;
import org.thingsboard.server.common.data.settings.UserDashboardAction;
import org.thingsboard.server.common.data.settings.UserDashboardsInfo;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.user.UserSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
@AllArgsConstructor
@Slf4j
public class DefaultTbUserSettingsService implements TbUserSettingsService {
private static final int MAX_DASHBOARD_INFO_LIST_SIZE = 10;
private static final Predicate<HasTitle> EMPTY_TITLE = i -> StringUtils.isEmpty(i.getTitle());
private final UserSettingsService settingsService;
private final DashboardService dashboardService;
@Override
public UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings) {
return settingsService.saveUserSettings(tenantId, userSettings);
}
@Override
public void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings) {
settingsService.updateUserSettings(tenantId, userId, type, settings);
}
@Override
public UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type) {
return settingsService.findUserSettings(tenantId, userId, type);
}
@Override
public void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List<String> jsonPaths) {
settingsService.deleteUserSettings(tenantId, userId, type, jsonPaths);
}
@Override
public UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id) {
UserSettings us = findUserSettings(tenantId, id, UserSettingsType.VISITED_DASHBOARDS);
if (us == null) {
return UserDashboardsInfo.EMPTY;
}
UserDashboardsInfo stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class);
return refreshDashboardTitles(tenantId, stored);
}
@Override
public UserDashboardsInfo reportUserDashboardAction(TenantId tenantId, UserId id, DashboardId dashboardId, UserDashboardAction action) {
UserSettings us = findUserSettings(tenantId, id, UserSettingsType.VISITED_DASHBOARDS);
UserDashboardsInfo stored = null;
if (us != null) {
stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class);
}
if (stored == null) {
stored = new UserDashboardsInfo();
}
switch (action) {
case STAR:
addToStarred(stored, dashboardId);
break;
case UNSTAR:
removeFromStarred(stored, dashboardId);
break;
case VISIT:
addToVisited(stored, dashboardId);
break;
}
stored = refreshDashboardTitles(tenantId, stored);
us = new UserSettings();
us.setUserId(id);
us.setType(UserSettingsType.VISITED_DASHBOARDS);
us.setSettings(JacksonUtil.valueToTree(stored));
saveUserSettings(tenantId, us);
return stored;
}
private void addToVisited(UserDashboardsInfo stored, DashboardId dashboardId) {
UUID id = dashboardId.getId();
long ts = System.currentTimeMillis();
var opt = stored.getLast().stream().filter(filterById(id)).findFirst();
if (opt.isPresent()) {
opt.get().setLastVisited(ts);
} else {
var newInfo = new LastVisitedDashboardInfo();
newInfo.setId(id);
newInfo.setStarred(stored.getStarred().stream().anyMatch(filterById(id)));
newInfo.setLastVisited(System.currentTimeMillis());
stored.getLast().add(newInfo);
}
stored.getLast().sort(Comparator.comparing(LastVisitedDashboardInfo::getLastVisited).reversed());
if (stored.getLast().size() > MAX_DASHBOARD_INFO_LIST_SIZE) {
stored.setLast(stored.getLast().stream().limit(MAX_DASHBOARD_INFO_LIST_SIZE).collect(Collectors.toList()));
}
}
private void removeFromStarred(UserDashboardsInfo stored, DashboardId dashboardId) {
UUID id = dashboardId.getId();
stored.getStarred().removeIf(filterById(id));
stored.getLast().stream().filter(d -> id.equals(d.getId())).findFirst().ifPresent(d -> d.setStarred(false));
}
private void addToStarred(UserDashboardsInfo stored, DashboardId dashboardId) {
UUID id = dashboardId.getId();
long ts = System.currentTimeMillis();
var opt = stored.getStarred().stream().filter(filterById(id)).findFirst();
if (opt.isPresent()) {
opt.get().setStarredAt(ts);
} else {
var newInfo = new StarredDashboardInfo();
newInfo.setId(id);
newInfo.setStarredAt(System.currentTimeMillis());
stored.getStarred().add(newInfo);
}
stored.getStarred().sort(Comparator.comparing(StarredDashboardInfo::getStarredAt).reversed());
if (stored.getStarred().size() > MAX_DASHBOARD_INFO_LIST_SIZE) {
stored.setStarred(stored.getStarred().stream().limit(MAX_DASHBOARD_INFO_LIST_SIZE).collect(Collectors.toList()));
}
Set<UUID> starredMap =
stored.getStarred().stream().map(AbstractUserDashboardInfo::getId).collect(Collectors.toSet());
stored.getLast().forEach(d -> d.setStarred(starredMap.contains(d.getId())));
}
private Predicate<AbstractUserDashboardInfo> filterById(UUID id) {
return d -> id.equals(d.getId());
}
private UserDashboardsInfo refreshDashboardTitles(TenantId tenantId, UserDashboardsInfo stored) {
if (stored == null) {
return UserDashboardsInfo.EMPTY;
}
stored.getLast().forEach(i -> i.setTitle(null));
stored.getStarred().forEach(i -> i.setTitle(null));
Set<UUID> uniqueIds = new HashSet<>();
stored.getLast().stream().map(AbstractUserDashboardInfo::getId).forEach(uniqueIds::add);
stored.getStarred().stream().map(AbstractUserDashboardInfo::getId).forEach(uniqueIds::add);
Map<UUID, String> dashboardTitles = new HashMap<>();
uniqueIds.forEach(id -> {
var title = dashboardService.findDashboardTitleById(tenantId, new DashboardId(id));
if (StringUtils.isNotEmpty(title)) {
dashboardTitles.put(id, title);
}
}
);
stored.getLast().forEach(i -> i.setTitle(dashboardTitles.get(i.getId())));
stored.getLast().removeIf(EMPTY_TITLE);
stored.getStarred().forEach(i -> i.setTitle(dashboardTitles.get(i.getId())));
stored.getStarred().removeIf(EMPTY_TITLE);
return stored;
}
}

42
application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.entitiy.user;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.settings.UserDashboardAction;
import org.thingsboard.server.common.data.settings.UserDashboardsInfo;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import java.util.List;
public interface TbUserSettingsService {
void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings);
UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings);
UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type);
void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List<String> jsonPaths);
UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id);
UserDashboardsInfo reportUserDashboardAction(TenantId tenantId, UserId id, DashboardId dashboardId, UserDashboardAction action);
}

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

@ -22,7 +22,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -91,6 +90,7 @@ import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
@ -177,6 +177,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired @Autowired
private NotificationSettingsService notificationSettingsService; private NotificationSettingsService notificationSettingsService;
@Autowired
private NotificationTargetService notificationTargetService;
@Bean @Bean
protected BCryptPasswordEncoder passwordEncoder() { protected BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
@ -679,27 +682,15 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Override @Override
public void createDefaultNotificationConfigs() { public void createDefaultNotificationConfigs() {
try { log.info("Creating default notification configs for system admin");
log.info("Creating default notification configs for system admin"); if (notificationTargetService.findNotificationTargetsByTenantId(TenantId.SYS_TENANT_ID, new PageLink(1)).getTotalElements() == 0) {
notificationSettingsService.createDefaultNotificationConfigs(TenantId.SYS_TENANT_ID); notificationSettingsService.createDefaultNotificationConfigs(TenantId.SYS_TENANT_ID);
} catch (Exception e) {
if (StringUtils.contains(e.getMessage(), "already exists")) {
log.info("Default notification configs are already present for system admin, skipping");
} else {
throw e;
}
} }
PageDataIterable<TenantId> tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500); PageDataIterable<TenantId> tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500);
log.info("Creating default notification configs for all tenants"); log.info("Creating default notification configs for all tenants");
for (TenantId tenantId : tenants) { for (TenantId tenantId : tenants) {
try { if (notificationTargetService.findNotificationTargetsByTenantId(tenantId, new PageLink(1)).getTotalElements() == 0) {
notificationSettingsService.createDefaultNotificationConfigs(tenantId); notificationSettingsService.createDefaultNotificationConfigs(tenantId);
} catch (Exception e) {
if (StringUtils.contains(e.getMessage(), "already exists")) {
log.info("Default notification configs are already present for tenant {}, skipping", tenantId);
} else {
throw e;
}
} }
} }
} }

23
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -65,6 +65,7 @@ public class InstallScripts {
public static final String JSON_DIR = "json"; public static final String JSON_DIR = "json";
public static final String SYSTEM_DIR = "system"; public static final String SYSTEM_DIR = "system";
public static final String TENANT_DIR = "tenant"; public static final String TENANT_DIR = "tenant";
public static final String EDGE_DIR = "edge";
public static final String DEVICE_PROFILE_DIR = "device_profile"; public static final String DEVICE_PROFILE_DIR = "device_profile";
public static final String DEMO_DIR = "demo"; public static final String DEMO_DIR = "demo";
public static final String RULE_CHAINS_DIR = "rule_chains"; public static final String RULE_CHAINS_DIR = "rule_chains";
@ -74,8 +75,6 @@ public class InstallScripts {
public static final String MODELS_LWM2M_DIR = "lwm2m-registry"; public static final String MODELS_LWM2M_DIR = "lwm2m-registry";
public static final String CREDENTIALS_DIR = "credentials"; public static final String CREDENTIALS_DIR = "credentials";
public static final String EDGE_MANAGEMENT = "edge_management";
public static final String JSON_EXT = ".json"; public static final String JSON_EXT = ".json";
public static final String XML_EXT = ".xml"; public static final String XML_EXT = ".xml";
@ -109,7 +108,7 @@ public class InstallScripts {
} }
private Path getEdgeRuleChainsDir() { private Path getEdgeRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, EDGE_MANAGEMENT, RULE_CHAINS_DIR); return Paths.get(getDataDir(), JSON_DIR, EDGE_DIR, RULE_CHAINS_DIR);
} }
public String getDataDir() { public String getDataDir() {
@ -293,17 +292,15 @@ public class InstallScripts {
} }
private void doSaveLwm2mResource(TbResource resource) throws ThingsboardException { private void doSaveLwm2mResource(TbResource resource) throws ThingsboardException {
try { log.trace("Executing saveResource [{}]", resource);
log.trace("Executing saveResource [{}]", resource); if (StringUtils.isEmpty(resource.getData())) {
if (StringUtils.isEmpty(resource.getData())) { throw new DataValidationException("Resource data should be specified!");
throw new DataValidationException("Resource data should be specified!"); }
} toLwm2mResource(resource);
toLwm2mResource(resource); TbResource foundResource =
resourceService.getResource(TenantId.SYS_TENANT_ID, ResourceType.LWM2M_MODEL, resource.getResourceKey());
if (foundResource == null) {
resourceService.saveResource(resource); resourceService.saveResource(resource);
} catch (DataValidationException e) {
log.debug("[{}] {}", resource.getFileName(), e.getMessage());
} catch (Exception ex) {
throw ex;
} }
} }
} }

44
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -35,7 +35,7 @@ import org.thingsboard.rule.engine.api.TbEmail;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageRecordState;
import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@ -64,8 +64,6 @@ public class DefaultMailService implements MailService {
public static final String MAIL_PROP = "mail."; public static final String MAIL_PROP = "mail.";
public static final String TARGET_EMAIL = "targetEmail"; public static final String TARGET_EMAIL = "targetEmail";
public static final String UTF_8 = "UTF-8"; public static final String UTF_8 = "UTF-8";
public static final int _10K = 10000;
public static final int _1M = 1000000;
private final MessageSource messages; private final MessageSource messages;
private final Configuration freemarkerConfig; private final Configuration freemarkerConfig;
@ -335,7 +333,7 @@ public class DefaultMailService implements MailService {
} }
@Override @Override
public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException { public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageRecordState recordState) throws ThingsboardException {
String subject = messages.getMessage("api.usage.state", null, Locale.US); String subject = messages.getMessage("api.usage.state", null, Locale.US);
Map<String, Object> model = new HashMap<>(); Map<String, Object> model = new HashMap<>();
@ -350,11 +348,11 @@ public class DefaultMailService implements MailService {
message = mergeTemplateIntoString("state.enabled.ftl", model); message = mergeTemplateIntoString("state.enabled.ftl", model);
break; break;
case WARNING: case WARNING:
model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(msg.getKey(), msg.getValue(), msg.getThreshold())); model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(recordState));
message = mergeTemplateIntoString("state.warning.ftl", model); message = mergeTemplateIntoString("state.warning.ftl", model);
break; break;
case DISABLED: case DISABLED:
model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getThreshold())); model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(recordState));
message = mergeTemplateIntoString("state.disabled.ftl", model); message = mergeTemplateIntoString("state.disabled.ftl", model);
break; break;
} }
@ -406,10 +404,10 @@ public class DefaultMailService implements MailService {
} }
} }
private String toWarningValueLabel(ApiUsageRecordKey key, long value, long threshold) { private String toWarningValueLabel(ApiUsageRecordState recordState) {
String valueInM = getValueAsString(value); String valueInM = recordState.getValueAsString();
String thresholdInM = getValueAsString(threshold); String thresholdInM = recordState.getThresholdAsString();
switch (key) { switch (recordState.getKey()) {
case STORAGE_DP_COUNT: case STORAGE_DP_COUNT:
case TRANSPORT_DP_COUNT: case TRANSPORT_DP_COUNT:
return valueInM + " out of " + thresholdInM + " allowed data points"; return valueInM + " out of " + thresholdInM + " allowed data points";
@ -428,36 +426,26 @@ public class DefaultMailService implements MailService {
} }
} }
private String toDisabledValueLabel(ApiUsageRecordKey key, long value) { private String toDisabledValueLabel(ApiUsageRecordState recordState) {
switch (key) { switch (recordState.getKey()) {
case STORAGE_DP_COUNT: case STORAGE_DP_COUNT:
case TRANSPORT_DP_COUNT: case TRANSPORT_DP_COUNT:
return getValueAsString(value) + " data points"; return recordState.getValueAsString() + " data points";
case TRANSPORT_MSG_COUNT: case TRANSPORT_MSG_COUNT:
return getValueAsString(value) + " messages"; return recordState.getValueAsString() + " messages";
case JS_EXEC_COUNT: case JS_EXEC_COUNT:
return "JavaScript functions " + getValueAsString(value) + " times"; return "JavaScript functions " + recordState.getValueAsString() + " times";
case RE_EXEC_COUNT: case RE_EXEC_COUNT:
return getValueAsString(value) + " Rule Engine messages"; return recordState.getValueAsString() + " Rule Engine messages";
case EMAIL_EXEC_COUNT: case EMAIL_EXEC_COUNT:
return getValueAsString(value) + " Email messages"; return recordState.getValueAsString() + " Email messages";
case SMS_EXEC_COUNT: case SMS_EXEC_COUNT:
return getValueAsString(value) + " SMS messages"; return recordState.getValueAsString() + " SMS messages";
default: default:
throw new RuntimeException("Not implemented!"); throw new RuntimeException("Not implemented!");
} }
} }
private String getValueAsString(long value) {
if (value > _1M && value % _1M < _10K) {
return value / _1M + "M";
} else if (value > _10K) {
return String.format("%.2fM", ((double) value) / 1000000);
} else {
return value + "";
}
}
private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email,
String subject, String message, long timeout) throws ThingsboardException { String subject, String message, long timeout) throws ThingsboardException {
try { try {

121
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -22,13 +22,12 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
@ -46,7 +45,6 @@ import org.thingsboard.server.common.data.notification.settings.NotificationSett
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilterType;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -84,6 +82,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ -103,61 +102,59 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
private final NotificationsTopicService notificationsTopicService; private final NotificationsTopicService notificationsTopicService;
private final TbQueueProducerProvider producerProvider; private final TbQueueProducerProvider producerProvider;
private final RateLimitService rateLimitService; private final RateLimitService rateLimitService;
private final MailService mailService;
private final SmsService smsService;
private Map<NotificationDeliveryMethod, NotificationChannel> channels; private Map<NotificationDeliveryMethod, NotificationChannel> channels;
@Override @Override
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest request, Consumer<NotificationRequestStats> callback) {
if (!rateLimitService.checkRateLimit(tenantId, LimitedApi.NOTIFICATION_REQUEST)) { if (!rateLimitService.checkRateLimit(tenantId, LimitedApi.NOTIFICATION_REQUEST)) {
throw new TbRateLimitsException(EntityType.TENANT); throw new TbRateLimitsException(EntityType.TENANT);
} }
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
NotificationTemplate notificationTemplate; NotificationTemplate notificationTemplate;
if (notificationRequest.getTemplateId() != null) { if (request.getTemplateId() != null) {
notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, notificationRequest.getTemplateId()); notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, request.getTemplateId());
} else { } else {
notificationTemplate = notificationRequest.getTemplate(); notificationTemplate = request.getTemplate();
} }
if (notificationTemplate == null) throw new IllegalArgumentException("Template is missing"); if (notificationTemplate == null) throw new IllegalArgumentException("Template is missing");
List<NotificationTarget> targets = notificationRequest.getTargets().stream().map(NotificationTargetId::new) List<NotificationTarget> targets = request.getTargets().stream().map(NotificationTargetId::new)
.map(id -> notificationTargetService.findNotificationTargetById(tenantId, id)).collect(Collectors.toList()); .map(id -> notificationTargetService.findNotificationTargetById(tenantId, id)).collect(Collectors.toList());
Set<NotificationDeliveryMethod> availableDeliveryMethods = getAvailableDeliveryMethods(tenantId);
NotificationRuleId ruleId = request.getRuleId();
notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (!template.isEnabled()) return; if (!template.isEnabled()) return;
if (!availableDeliveryMethods.contains(deliveryMethod)) { if (!channels.get(deliveryMethod).check(tenantId)) {
throw new IllegalArgumentException("Settings for " + deliveryMethod.getName() + " are missing"); throw new IllegalArgumentException("Unable to send notification via " + deliveryMethod.getName() + ": not configured or not working");
} }
if (notificationRequest.getRuleId() == null) { if (ruleId == null) {
if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) {
throw new IllegalArgumentException("Target for " + deliveryMethod.getName() + " delivery method is missing"); throw new IllegalArgumentException("Target for " + deliveryMethod.getName() + " delivery method is missing");
} }
} }
}); });
if (notificationRequest.getAdditionalConfig() != null) { if (request.getAdditionalConfig() != null) {
NotificationRequestConfig config = notificationRequest.getAdditionalConfig(); NotificationRequestConfig config = request.getAdditionalConfig();
if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) { if (config.getSendingDelayInSec() > 0 && request.getId() == null) {
notificationRequest.setStatus(NotificationRequestStatus.SCHEDULED); request.setStatus(NotificationRequestStatus.SCHEDULED);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); request = notificationRequestService.saveNotificationRequest(tenantId, request);
forwardToNotificationSchedulerService(tenantId, savedNotificationRequest.getId()); forwardToNotificationSchedulerService(tenantId, request.getId());
return savedNotificationRequest; return request;
} }
} }
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
log.debug("Processing notification request (tenantId: {}, targets: {})", tenantId, notificationRequest.getTargets()); log.debug("Processing notification request (tenantId: {}, targets: {})", tenantId, request.getTargets());
notificationRequest.setStatus(NotificationRequestStatus.PROCESSING); request.setStatus(NotificationRequestStatus.PROCESSING);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); request = notificationRequestService.saveNotificationRequest(tenantId, request);
NotificationProcessingContext ctx = NotificationProcessingContext.builder() NotificationProcessingContext ctx = NotificationProcessingContext.builder()
.tenantId(tenantId) .tenantId(tenantId)
.request(savedNotificationRequest) .request(request)
.settings(settings)
.template(notificationTemplate) .template(notificationTemplate)
.settings(settings)
.build(); .build();
notificationExecutor.submit(() -> { notificationExecutor.submit(() -> {
@ -169,7 +166,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} }
Futures.whenAllComplete(results).run(() -> { Futures.whenAllComplete(results).run(() -> {
NotificationRequestId requestId = savedNotificationRequest.getId(); NotificationRequestId requestId = ctx.getRequest().getId();
log.debug("[{}] Notification request processing is finished", requestId); log.debug("[{}] Notification request processing is finished", requestId);
NotificationRequestStats stats = ctx.getStats(); NotificationRequestStats stats = ctx.getStats();
try { try {
@ -177,36 +174,39 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] Failed to update stats for notification request", requestId, e); log.error("[{}] Failed to update stats for notification request", requestId, e);
} }
if (callback != null) {
try {
callback.accept(stats);
} catch (Exception e) {
log.error("Failed to process callback for notification request {}", requestId, e);
}
}
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
}); });
return savedNotificationRequest; return request;
} }
private List<ListenableFuture<Void>> processForTarget(NotificationTarget target, NotificationProcessingContext ctx) { private List<ListenableFuture<Void>> processForTarget(NotificationTarget target, NotificationProcessingContext ctx) {
Iterable<? extends NotificationRecipient> recipients; Iterable<? extends NotificationRecipient> recipients;
switch (target.getConfiguration().getType()) { switch (target.getConfiguration().getType()) {
case PLATFORM_USERS: { case PLATFORM_USERS: {
PlatformUsersNotificationTargetConfig platformUsersTargetConfig = (PlatformUsersNotificationTargetConfig) target.getConfiguration(); PlatformUsersNotificationTargetConfig targetConfig = (PlatformUsersNotificationTargetConfig) target.getConfiguration();
if (platformUsersTargetConfig.getUsersFilter().getType() == UsersFilterType.AFFECTED_USER) { if (targetConfig.getUsersFilter().getType().isForRules()) {
if (ctx.getRequest().getInfo() instanceof RuleOriginatedNotificationInfo) { recipients = new PageDataIterable<>(pageLink -> {
UserId targetUserId = ((RuleOriginatedNotificationInfo) ctx.getRequest().getInfo()).getTargetUserId(); return notificationTargetService.findRecipientsForRuleNotificationTargetConfig(ctx.getTenantId(), targetConfig, (RuleOriginatedNotificationInfo) ctx.getRequest().getInfo(), pageLink);
if (targetUserId != null) { }, 500);
recipients = List.of(userService.findUserById(ctx.getTenantId(), targetUserId));
break;
}
}
recipients = Collections.emptyList();
} else { } else {
recipients = new PageDataIterable<>(pageLink -> { recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), ctx.getCustomerId(), platformUsersTargetConfig, pageLink); return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink);
}, 500); }, 500);
} }
break; break;
} }
case SLACK: { case SLACK: {
SlackNotificationTargetConfig slackTargetConfig = (SlackNotificationTargetConfig) target.getConfiguration(); SlackNotificationTargetConfig targetConfig = (SlackNotificationTargetConfig) target.getConfiguration();
recipients = List.of(slackTargetConfig.getConversation()); recipients = List.of(targetConfig.getConversation());
break; break;
} }
default: { default: {
@ -239,15 +239,10 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
if (ctx.getStats().contains(deliveryMethod, recipient.getId())) { if (ctx.getStats().contains(deliveryMethod, recipient.getId())) {
return Futures.immediateFailedFuture(new AlreadySentException()); return Futures.immediateFailedFuture(new AlreadySentException());
} }
Map<String, String> templateContext;
if (recipient instanceof User) {
templateContext = ctx.createTemplateContext(((User) recipient));
} else {
templateContext = Collections.emptyMap();
}
DeliveryMethodNotificationTemplate processedTemplate; DeliveryMethodNotificationTemplate processedTemplate;
try { try {
processedTemplate = ctx.getProcessedTemplate(deliveryMethod, templateContext); processedTemplate = ctx.getProcessedTemplate(deliveryMethod, recipient);
} catch (Exception e) { } catch (Exception e) {
return Futures.immediateFailedFuture(e); return Futures.immediateFailedFuture(e);
} }
@ -309,7 +304,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId); log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId);
NotificationUpdate update = NotificationUpdate.builder() NotificationUpdate update = NotificationUpdate.builder()
.updated(true) .updated(true)
.notificationId(notificationId) .notificationId(notificationId.getId())
.newStatus(NotificationStatus.READ) .newStatus(NotificationStatus.READ)
.build(); .build();
onNotificationUpdate(tenantId, recipientId, update); onNotificationUpdate(tenantId, recipientId, update);
@ -345,20 +340,15 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override @Override
public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId) { public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId) {
Set<NotificationDeliveryMethod> deliveryMethods = new HashSet<>(); return channels.values().stream()
deliveryMethods.add(NotificationDeliveryMethod.WEB); .filter(channel -> channel.check(tenantId))
NotificationSettings notificationSettings = notificationSettingsService.findNotificationSettings(tenantId); .map(NotificationChannel::getDeliveryMethod)
if (notificationSettings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.SLACK)) { .collect(Collectors.toSet());
deliveryMethods.add(NotificationDeliveryMethod.SLACK); }
}
try { @Override
mailService.testConnection(tenantId); public boolean check(TenantId tenantId) {
deliveryMethods.add(NotificationDeliveryMethod.EMAIL); return true;
} catch (Exception e) {}
if (smsService.isConfigured(tenantId)) {
deliveryMethods.add(NotificationDeliveryMethod.SMS);
}
return deliveryMethods;
} }
@Override @Override
@ -374,6 +364,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
.deleted(true) .deleted(true)
.build()); .build());
} else if (notificationRequest.isScheduled()) { } else if (notificationRequest.isScheduled()) {
// TODO: just forward to scheduler service
clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED); clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED);
} }
} }

13
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java

@ -25,9 +25,10 @@ import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
@ -109,14 +110,12 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS
notificationExecutor.executeAsync(() -> { notificationExecutor.executeAsync(() -> {
try { try {
notificationCenter.processNotificationRequest(tenantId, notificationRequest); notificationCenter.processNotificationRequest(tenantId, notificationRequest, null);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to process scheduled notification request {}", notificationRequest.getId(), e); log.error("Failed to process scheduled notification request {}", notificationRequest.getId(), e);
UserId senderId = notificationRequest.getSenderId(); NotificationRequestStats stats = new NotificationRequestStats();
if (senderId != null) { stats.setError(e.getMessage());
notificationCenter.sendBasicNotification(tenantId, senderId, "Notification failure", notificationRequestService.updateNotificationRequest(tenantId, request.getId(), NotificationRequestStatus.SENT, stats);
"Failed to process scheduled notification (request " + notificationRequest.getId() + "): " + e.getMessage());
}
} }
}); });
scheduledNotificationRequests.remove(notificationRequest.getId()); scheduledNotificationRequests.remove(notificationRequest.getId());

114
application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java

@ -15,35 +15,30 @@
*/ */
package org.thingsboard.server.service.notification; package org.thingsboard.server.service.notification;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.collections4.MapUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.HasSubject; import org.thingsboard.server.common.data.notification.template.HasSubject;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.util.TemplateUtils;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class NotificationProcessingContext { public class NotificationProcessingContext {
@ -62,11 +57,9 @@ public class NotificationProcessingContext {
@Getter @Getter
private final NotificationRequestStats stats; private final NotificationRequestStats stats;
private static final Pattern TEMPLATE_PARAM_PATTERN = Pattern.compile("\\$\\{([a-zA-Z]+)(:[a-zA-Z]+)?}");
@Builder @Builder
public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationSettings settings, public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationTemplate template, NotificationSettings settings) {
NotificationTemplate template) {
this.tenantId = tenantId; this.tenantId = tenantId;
this.request = request; this.request = request;
this.settings = settings; this.settings = settings;
@ -80,6 +73,7 @@ public class NotificationProcessingContext {
NotificationTemplateConfig templateConfig = notificationTemplate.getConfiguration(); NotificationTemplateConfig templateConfig = notificationTemplate.getConfiguration();
templateConfig.getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { templateConfig.getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (template.isEnabled()) { if (template.isEnabled()) {
template = processTemplate(template, null); // processing template with immutable params
templates.put(deliveryMethod, template); templates.put(deliveryMethod, template);
} }
}); });
@ -90,74 +84,54 @@ public class NotificationProcessingContext {
return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod); return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod);
} }
public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) { public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient) {
NotificationInfo info = request.getInfo(); T template = (T) templates.get(deliveryMethod);
if (info != null) { Map<String, String> additionalTemplateContext = null;
templateContext = new HashMap<>(templateContext); if (recipient != null) {
templateContext.putAll(info.getTemplateData()); additionalTemplateContext = createTemplateContextForRecipient(recipient);
}
if (MapUtils.isNotEmpty(additionalTemplateContext) && template.containsAny(additionalTemplateContext.keySet().toArray(String[]::new))) {
template = processTemplate(template, additionalTemplateContext);
} }
return template;
}
T template = (T) templates.get(deliveryMethod).copy(); private <T extends DeliveryMethodNotificationTemplate> T processTemplate(T template, Map<String, String> additionalTemplateContext) {
template.setBody(processTemplate(template.getBody(), templateContext)); Map<String, String> templateContext = new HashMap<>();
if (request.getInfo() != null) {
templateContext.putAll(request.getInfo().getTemplateData());
}
if (additionalTemplateContext != null) {
templateContext.putAll(additionalTemplateContext);
}
if (templateContext.isEmpty()) return template;
template = (T) template.copy();
template.setBody(TemplateUtils.processTemplate(template.getBody(), templateContext));
if (template instanceof HasSubject) { if (template instanceof HasSubject) {
String subject = ((HasSubject) template).getSubject(); String subject = ((HasSubject) template).getSubject();
((HasSubject) template).setSubject(processTemplate(subject, templateContext)); ((HasSubject) template).setSubject(TemplateUtils.processTemplate(subject, templateContext));
} }
if (template instanceof WebDeliveryMethodNotificationTemplate) {
if (deliveryMethod == NotificationDeliveryMethod.WEB) {
WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template; WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template;
Optional<ObjectNode> buttonConfig = Optional.ofNullable(webNotificationTemplate.getAdditionalConfig()) String buttonText = webNotificationTemplate.getButtonText();
.map(config -> config.get("actionButtonConfig")).filter(JsonNode::isObject) if (isNotEmpty(buttonText)) {
.map(config -> (ObjectNode) config); webNotificationTemplate.setButtonText(TemplateUtils.processTemplate(buttonText, templateContext));
if (buttonConfig.isPresent()) { }
JsonNode text = buttonConfig.get().get("text"); String buttonLink = webNotificationTemplate.getButtonLink();
if (text != null && text.isTextual()) { if (isNotEmpty(buttonLink)) {
text = new TextNode(processTemplate(text.asText(), templateContext)); webNotificationTemplate.setButtonLink(TemplateUtils.processTemplate(buttonLink, templateContext));
buttonConfig.get().set("text", text);
}
JsonNode link = buttonConfig.get().get("link");
if (link != null && link.isTextual()) {
link = new TextNode(processTemplate(link.asText(), templateContext));
buttonConfig.get().set("link", link);
}
} }
} }
return template; return template;
} }
private static String processTemplate(String template, Map<String, String> context) { private Map<String, String> createTemplateContextForRecipient(NotificationRecipient recipient) {
return TEMPLATE_PARAM_PATTERN.matcher(template).replaceAll(matchResult -> { return Map.of(
String key = matchResult.group(1); "recipientEmail", Strings.nullToEmpty(recipient.getEmail()),
String value = Strings.nullToEmpty(context.get(key)); "recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()),
String function = matchResult.group(2); "recipientLastName", Strings.nullToEmpty(recipient.getLastName())
if (function != null) { );
switch (function) {
case ":upperCase":
return value.toUpperCase();
case ":lowerCase":
return value.toLowerCase();
case ":capitalize":
return StringUtils.capitalize(value.toLowerCase());
}
}
return value;
});
}
public Map<String, String> createTemplateContext(User recipient) {
Map<String, String> templateContext = new HashMap<>();
templateContext.put("recipientEmail", recipient.getEmail());
templateContext.put("recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()));
templateContext.put("recipientLastName", Strings.nullToEmpty(recipient.getLastName()));
return templateContext;
}
public CustomerId getCustomerId() {
if (request.getInfo() instanceof RuleOriginatedNotificationInfo) {
return ((RuleOriginatedNotificationInfo) request.getInfo()).getOriginatorEntityCustomerId();
} else {
return null;
}
} }
} }

19
application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java

@ -19,7 +19,9 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.TbEmail;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.mail.MailExecutorService;
@ -35,11 +37,26 @@ public class EmailNotificationChannel implements NotificationChannel<User, Email
@Override @Override
public ListenableFuture<Void> sendNotification(User recipient, EmailDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { public ListenableFuture<Void> sendNotification(User recipient, EmailDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) {
return executor.submit(() -> { return executor.submit(() -> {
mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), processedTemplate.getSubject(), processedTemplate.getBody()); mailService.send(recipient.getTenantId(), null, TbEmail.builder()
.to(recipient.getEmail())
.subject(processedTemplate.getSubject())
.body(processedTemplate.getBody())
.html(true)
.build());
return null; return null;
}); });
} }
@Override
public boolean check(TenantId tenantId) {
try {
mailService.testConnection(tenantId);
return true;
} catch (Exception e) {
return false;
}
}
@Override @Override
public NotificationDeliveryMethod getDeliveryMethod() { public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.EMAIL; return NotificationDeliveryMethod.EMAIL;

5
application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java

@ -16,15 +16,18 @@
package org.thingsboard.server.service.notification.channels; package org.thingsboard.server.service.notification.channels;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
public interface NotificationChannel<R extends NotificationRecipient, T extends DeliveryMethodNotificationTemplate> { public interface NotificationChannel<R extends NotificationRecipient, T extends DeliveryMethodNotificationTemplate> {
ListenableFuture<Void> sendNotification(R recipient, T processedTemplate, NotificationProcessingContext ctx); ListenableFuture<Void> sendNotification(R recipient, T processedTemplate, NotificationProcessingContext ctx);
boolean check(TenantId tenantId);
NotificationDeliveryMethod getDeliveryMethod(); NotificationDeliveryMethod getDeliveryMethod();
} }

10
application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java

@ -19,7 +19,10 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
@ -31,6 +34,7 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService;
public class SlackNotificationChannel implements NotificationChannel<SlackConversation, SlackDeliveryMethodNotificationTemplate> { public class SlackNotificationChannel implements NotificationChannel<SlackConversation, SlackDeliveryMethodNotificationTemplate> {
private final SlackService slackService; private final SlackService slackService;
private final NotificationSettingsService notificationSettingsService;
private final ExternalCallExecutorService executor; private final ExternalCallExecutorService executor;
@Override @Override
@ -42,6 +46,12 @@ public class SlackNotificationChannel implements NotificationChannel<SlackConver
}); });
} }
@Override
public boolean check(TenantId tenantId) {
NotificationSettings notificationSettings = notificationSettingsService.findNotificationSettings(tenantId);
return notificationSettings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.SLACK);
}
@Override @Override
public NotificationDeliveryMethod getDeliveryMethod() { public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.SLACK; return NotificationDeliveryMethod.SLACK;

6
application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java

@ -22,6 +22,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.service.notification.NotificationProcessingContext;
@ -47,6 +48,11 @@ public class SmsNotificationChannel implements NotificationChannel<User, SmsDeli
}); });
} }
@Override
public boolean check(TenantId tenantId) {
return smsService.isConfigured(tenantId);
}
@Override @Override
public NotificationDeliveryMethod getDeliveryMethod() { public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.SMS; return NotificationDeliveryMethod.SMS;

78
application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java → application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java

@ -32,16 +32,15 @@ import org.thingsboard.server.common.data.notification.NotificationRequestConfig
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.service.executors.NotificationExecutorService; import org.thingsboard.server.service.executors.NotificationExecutorService;
import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor; import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor; import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor;
@ -59,7 +58,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
public class DefaultNotificationRuleProcessingService implements NotificationRuleProcessingService { public class DefaultNotificationRuleProcessor implements NotificationRuleProcessor {
private final NotificationRuleService notificationRuleService; private final NotificationRuleService notificationRuleService;
private final NotificationRequestService notificationRequestService; private final NotificationRequestService notificationRequestService;
@ -69,16 +68,17 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
private final Map<NotificationRuleTriggerType, NotificationRuleTriggerProcessor> triggerProcessors = new EnumMap<>(NotificationRuleTriggerType.class); private final Map<NotificationRuleTriggerType, NotificationRuleTriggerProcessor> triggerProcessors = new EnumMap<>(NotificationRuleTriggerType.class);
private final Map<String, NotificationRuleTriggerType> ruleEngineMsgTypeToTriggerType = new HashMap<>();
@Override @Override
public void process(TenantId tenantId, NotificationRuleTrigger trigger) { public void process(NotificationRuleTrigger trigger) {
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType( NotificationRuleTriggerType triggerType = trigger.getType();
trigger.getType().isTenantLevel() ? tenantId : TenantId.SYS_TENANT_ID, trigger.getType()); if (triggerType == null) return;
TenantId tenantId = triggerType.isTenantLevel() ? trigger.getTenantId() : TenantId.SYS_TENANT_ID;
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType);
for (NotificationRule rule : rules) { for (NotificationRule rule : rules) {
notificationExecutor.submit(() -> { notificationExecutor.submit(() -> {
try { try {
processNotificationRule(tenantId, rule, trigger); processNotificationRule(rule, trigger);
} catch (Throwable e) { } catch (Throwable e) {
log.error("Failed to process notification rule {} for trigger type {} with trigger object {}", rule.getId(), rule.getTriggerType(), trigger, e); log.error("Failed to process notification rule {} for trigger type {} with trigger object {}", rule.getId(), rule.getTriggerType(), trigger, e);
} }
@ -86,24 +86,12 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
} }
} }
@Override private void processNotificationRule(NotificationRule rule, NotificationRuleTrigger trigger) {
public void process(TenantId tenantId, TbMsg ruleEngineMsg) {
NotificationRuleTriggerType triggerType = ruleEngineMsgTypeToTriggerType.get(ruleEngineMsg.getType());
if (triggerType == null) {
return;
}
process(tenantId, RuleEngineMsgTrigger.builder()
.msg(ruleEngineMsg)
.triggerType(triggerType)
.build());
}
private void processNotificationRule(TenantId tenantId, NotificationRule rule, NotificationRuleTrigger trigger) {
NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig(); NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig();
log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType()); log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType());
if (matchesClearRule(trigger, triggerConfig)) { if (matchesClearRule(trigger, triggerConfig)) {
List<NotificationRequest> notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(tenantId, rule.getId(), trigger.getOriginatorEntityId()); List<NotificationRequest> notificationRequests = findAlreadySentNotificationRequests(rule, trigger);
if (notificationRequests.isEmpty()) { if (notificationRequests.isEmpty()) {
return; return;
} }
@ -113,11 +101,11 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
.flatMap(notificationRequest -> notificationRequest.getTargets().stream()) .flatMap(notificationRequest -> notificationRequest.getTargets().stream())
.distinct().collect(Collectors.toList()); .distinct().collect(Collectors.toList());
NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig); NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig);
submitNotificationRequest(tenantId, targets, rule, trigger.getOriginatorEntityId(), notificationInfo, 0); submitNotificationRequest(targets, rule, trigger.getOriginatorEntityId(), notificationInfo, 0);
notificationRequests.forEach(notificationRequest -> { notificationRequests.forEach(notificationRequest -> {
if (notificationRequest.isScheduled()) { if (notificationRequest.isScheduled()) {
notificationCenter.deleteNotificationRequest(tenantId, notificationRequest.getId()); notificationCenter.deleteNotificationRequest(rule.getTenantId(), notificationRequest.getId());
} }
}); });
return; return;
@ -126,31 +114,23 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
if (matchesFilter(trigger, triggerConfig)) { if (matchesFilter(trigger, triggerConfig)) {
NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig); NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig);
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> { rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
submitNotificationRequest(tenantId, targets, rule, trigger.getOriginatorEntityId(), notificationInfo, delay); submitNotificationRequest(targets, rule, trigger.getOriginatorEntityId(), notificationInfo, delay);
}); });
} }
} }
private boolean matchesFilter(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) { private List<NotificationRequest> findAlreadySentNotificationRequests(NotificationRule rule, NotificationRuleTrigger trigger) {
return triggerProcessors.get(triggerConfig.getTriggerType()).matchesFilter(trigger, triggerConfig); return notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(rule.getTenantId(), rule.getId(), trigger.getOriginatorEntityId());
} }
private boolean matchesClearRule(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) { private void submitNotificationRequest(List<UUID> targets, NotificationRule rule,
return triggerProcessors.get(triggerConfig.getTriggerType()).matchesClearRule(trigger, triggerConfig);
}
private NotificationInfo constructNotificationInfo(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) {
return triggerProcessors.get(triggerConfig.getTriggerType()).constructNotificationInfo(trigger, triggerConfig);
}
private void submitNotificationRequest(TenantId tenantId, List<UUID> targets, NotificationRule rule,
EntityId originatorEntityId, NotificationInfo notificationInfo, int delayInSec) { EntityId originatorEntityId, NotificationInfo notificationInfo, int delayInSec) {
NotificationRequestConfig config = new NotificationRequestConfig(); NotificationRequestConfig config = new NotificationRequestConfig();
if (delayInSec > 0) { if (delayInSec > 0) {
config.setSendingDelayInSec(delayInSec); config.setSendingDelayInSec(delayInSec);
} }
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId) .tenantId(rule.getTenantId())
.targets(targets) .targets(targets)
.templateId(rule.getTemplateId()) .templateId(rule.getTemplateId())
.additionalConfig(config) .additionalConfig(config)
@ -161,13 +141,25 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
notificationExecutor.submit(() -> { notificationExecutor.submit(() -> {
try { try {
log.debug("Submitting notification request for rule '{}' with delay of {} sec to targets {}", rule.getName(), delayInSec, targets); log.debug("Submitting notification request for rule '{}' with delay of {} sec to targets {}", rule.getName(), delayInSec, targets);
notificationCenter.processNotificationRequest(tenantId, notificationRequest); notificationCenter.processNotificationRequest(rule.getTenantId(), notificationRequest, null);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to process notification request for rule {}", rule.getId(), e); log.error("Failed to process notification request for tenant {} for rule {}", rule.getTenantId(), rule.getId(), e);
} }
}); });
} }
private boolean matchesFilter(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) {
return triggerProcessors.get(triggerConfig.getTriggerType()).matchesFilter(trigger, triggerConfig);
}
private boolean matchesClearRule(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) {
return triggerProcessors.get(triggerConfig.getTriggerType()).matchesClearRule(trigger, triggerConfig);
}
private NotificationInfo constructNotificationInfo(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) {
return triggerProcessors.get(triggerConfig.getTriggerType()).constructNotificationInfo(trigger);
}
@EventListener(ComponentLifecycleMsg.class) @EventListener(ComponentLifecycleMsg.class)
public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) { public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) {
if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED || if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED ||
@ -187,6 +179,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
@Autowired @Autowired
public void setTriggerProcessors(Collection<NotificationRuleTriggerProcessor> processors) { public void setTriggerProcessors(Collection<NotificationRuleTriggerProcessor> processors) {
Map<String, NotificationRuleTriggerType> ruleEngineMsgTypeToTriggerType = new HashMap<>();
processors.forEach(processor -> { processors.forEach(processor -> {
triggerProcessors.put(processor.getTriggerType(), processor); triggerProcessors.put(processor.getTriggerType(), processor);
if (processor instanceof RuleEngineMsgNotificationRuleTriggerProcessor) { if (processor instanceof RuleEngineMsgNotificationRuleTriggerProcessor) {
@ -196,6 +189,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
}); });
} }
}); });
RuleEngineMsgTrigger.msgTypeToTriggerType = ruleEngineMsgTypeToTriggerType;
} }
} }

10
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java

@ -23,11 +23,11 @@ import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmAssignmentNotificationInfo; import org.thingsboard.server.common.data.notification.info.AlarmAssignmentNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig.Action; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig.Action;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import java.util.Set; import java.util.Set;
@ -49,7 +49,7 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
} }
@Override @Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, AlarmAssignmentNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
AlarmInfo alarmInfo = JacksonUtil.fromString(trigger.getMsg().getData(), AlarmInfo.class); AlarmInfo alarmInfo = JacksonUtil.fromString(trigger.getMsg().getData(), AlarmInfo.class);
AlarmAssignee assignee = alarmInfo.getAssignee(); AlarmAssignee assignee = alarmInfo.getAssignee();
return AlarmAssignmentNotificationInfo.builder() return AlarmAssignmentNotificationInfo.builder()
@ -58,7 +58,9 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
.assigneeLastName(assignee != null ? assignee.getLastName() : null) .assigneeLastName(assignee != null ? assignee.getLastName() : null)
.assigneeEmail(assignee != null ? assignee.getEmail() : null) .assigneeEmail(assignee != null ? assignee.getEmail() : null)
.assigneeId(assignee != null ? assignee.getId() : null) .assigneeId(assignee != null ? assignee.getId() : null)
.userName(trigger.getMsg().getMetaData().getValue("userName")) .userEmail(trigger.getMsg().getMetaData().getValue("userEmail"))
.userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName"))
.userLastName(trigger.getMsg().getMetaData().getValue("userLastName"))
.alarmId(alarmInfo.getUuidId()) .alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType()) .alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator()) .alarmOriginator(alarmInfo.getOriginator())

10
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java

@ -24,11 +24,11 @@ import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo; import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmCommentNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmCommentNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import java.util.Set; import java.util.Set;
@ -59,14 +59,16 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
} }
@Override @Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, AlarmCommentNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
TbMsg msg = trigger.getMsg(); TbMsg msg = trigger.getMsg();
AlarmComment comment = JacksonUtil.fromString(msg.getMetaData().getValue("comment"), AlarmComment.class); AlarmComment comment = JacksonUtil.fromString(msg.getMetaData().getValue("comment"), AlarmComment.class);
AlarmInfo alarmInfo = JacksonUtil.fromString(msg.getData(), AlarmInfo.class); AlarmInfo alarmInfo = JacksonUtil.fromString(msg.getData(), AlarmInfo.class);
return AlarmCommentNotificationInfo.builder() return AlarmCommentNotificationInfo.builder()
.comment(comment.getComment().get("text").asText()) .comment(comment.getComment().get("text").asText())
.action(msg.getType().equals(DataConstants.COMMENT_CREATED) ? "added" : "updated") .action(msg.getType().equals(DataConstants.COMMENT_CREATED) ? "added" : "updated")
.userName(msg.getMetaData().getValue("userName")) .userEmail(trigger.getMsg().getMetaData().getValue("userEmail"))
.userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName"))
.userLastName(trigger.getMsg().getMetaData().getValue("userLastName"))
.alarmId(alarmInfo.getUuidId()) .alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType()) .alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator()) .alarmOriginator(alarmInfo.getOriginator())

8
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java

@ -20,13 +20,13 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo; import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.notification.trigger.AlarmTrigger; import org.thingsboard.server.common.msg.notification.trigger.AlarmTrigger;
import static org.apache.commons.collections.CollectionUtils.isEmpty; import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty; import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@ -93,7 +93,7 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
} }
@Override @Override
public NotificationInfo constructNotificationInfo(AlarmTrigger trigger, AlarmNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(AlarmTrigger trigger) {
AlarmApiCallResult alarmUpdate = trigger.getAlarmUpdate(); AlarmApiCallResult alarmUpdate = trigger.getAlarmUpdate();
AlarmInfo alarmInfo = alarmUpdate.getAlarm(); AlarmInfo alarmInfo = alarmUpdate.getAlarm();
return AlarmNotificationInfo.builder() return AlarmNotificationInfo.builder()

59
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ApiUsageLimitTriggerProcessor.java

@ -0,0 +1,59 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.info.ApiUsageLimitNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.ApiUsageLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.notification.trigger.ApiUsageLimitTrigger;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
@Service
@RequiredArgsConstructor
public class ApiUsageLimitTriggerProcessor implements NotificationRuleTriggerProcessor<ApiUsageLimitTrigger, ApiUsageLimitNotificationRuleTriggerConfig> {
private final TenantService tenantService;
@Override
public boolean matchesFilter(ApiUsageLimitTrigger trigger, ApiUsageLimitNotificationRuleTriggerConfig triggerConfig) {
return (isEmpty(triggerConfig.getApiFeatures()) || triggerConfig.getApiFeatures().contains(trigger.getState().getApiFeature())) &&
(isEmpty(triggerConfig.getNotifyOn()) || triggerConfig.getNotifyOn().contains(trigger.getStatus()));
}
@Override
public RuleOriginatedNotificationInfo constructNotificationInfo(ApiUsageLimitTrigger trigger) {
return ApiUsageLimitNotificationInfo.builder()
.feature(trigger.getState().getApiFeature())
.recordKey(trigger.getState().getKey())
.status(trigger.getStatus())
.limit(trigger.getState().getThresholdAsString())
.currentValue(trigger.getState().getValueAsString())
.tenantId(trigger.getTenantId())
.tenantName(tenantService.findTenantById(trigger.getTenantId()).getName())
.build();
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.API_USAGE_LIMIT;
}
}

26
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityTriggerProcessor.java → application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java

@ -22,24 +22,29 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.info.DeviceInactivityNotificationInfo; import org.thingsboard.server.common.data.notification.info.DeviceActivityNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceInactivityNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig.DeviceEvent;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import java.util.Set; import java.util.Set;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<DeviceInactivityNotificationRuleTriggerConfig> { public class DeviceActivityTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<DeviceActivityNotificationRuleTriggerConfig> {
private final TbDeviceProfileCache deviceProfileCache; private final TbDeviceProfileCache deviceProfileCache;
@Override @Override
public boolean matchesFilter(RuleEngineMsgTrigger trigger, DeviceInactivityNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(RuleEngineMsgTrigger trigger, DeviceActivityNotificationRuleTriggerConfig triggerConfig) {
DeviceEvent event = trigger.getMsg().getType().equals(DataConstants.ACTIVITY_EVENT) ? DeviceEvent.ACTIVE : DeviceEvent.INACTIVE;
if (!triggerConfig.getNotifyOn().contains(event)) {
return false;
}
DeviceId deviceId = (DeviceId) trigger.getMsg().getOriginator(); DeviceId deviceId = (DeviceId) trigger.getMsg().getOriginator();
if (CollectionUtils.isNotEmpty(triggerConfig.getDevices())) { if (CollectionUtils.isNotEmpty(triggerConfig.getDevices())) {
return triggerConfig.getDevices().contains(deviceId.getId()); return triggerConfig.getDevices().contains(deviceId.getId());
@ -52,9 +57,10 @@ public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificati
} }
@Override @Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, DeviceInactivityNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
TbMsg msg = trigger.getMsg(); TbMsg msg = trigger.getMsg();
return DeviceInactivityNotificationInfo.builder() return DeviceActivityNotificationInfo.builder()
.eventType(trigger.getMsg().getType().equals(DataConstants.ACTIVITY_EVENT) ? "active" : "inactive")
.deviceId(msg.getOriginator().getId()) .deviceId(msg.getOriginator().getId())
.deviceName(msg.getMetaData().getValue("deviceName")) .deviceName(msg.getMetaData().getValue("deviceName"))
.deviceType(msg.getMetaData().getValue("deviceType")) .deviceType(msg.getMetaData().getValue("deviceType"))
@ -65,12 +71,12 @@ public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificati
@Override @Override
public NotificationRuleTriggerType getTriggerType() { public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.DEVICE_INACTIVITY; return NotificationRuleTriggerType.DEVICE_ACTIVITY;
} }
@Override @Override
public Set<String> getSupportedMsgTypes() { public Set<String> getSupportedMsgTypes() {
return Set.of(DataConstants.INACTIVITY_EVENT); return Set.of(DataConstants.ACTIVITY_EVENT, DataConstants.INACTIVITY_EVENT);
} }
} }

6
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java

@ -18,10 +18,10 @@ package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.info.EntitiesLimitNotificationInfo; import org.thingsboard.server.common.data.notification.info.EntitiesLimitNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.notification.trigger.EntitiesLimitTrigger; import org.thingsboard.server.common.msg.notification.trigger.EntitiesLimitTrigger;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty; import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@ -41,7 +41,7 @@ public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerPro
} }
@Override @Override
public NotificationInfo constructNotificationInfo(EntitiesLimitTrigger trigger, EntitiesLimitNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(EntitiesLimitTrigger trigger) {
return EntitiesLimitNotificationInfo.builder() return EntitiesLimitNotificationInfo.builder()
.entityType(trigger.getEntityType()) .entityType(trigger.getEntityType())
.currentCount(trigger.getCurrentCount()) .currentCount(trigger.getCurrentCount())

16
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java

@ -20,16 +20,18 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo; import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
@Service @Service
public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<EntityActionNotificationRuleTriggerConfig> { public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<EntityActionNotificationRuleTriggerConfig> {
@ -51,11 +53,11 @@ public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRu
} else { } else {
return false; return false;
} }
return triggerConfig.getEntityType() == null || getEntityType(trigger.getMsg()) == triggerConfig.getEntityType(); return isEmpty(triggerConfig.getEntityTypes()) || triggerConfig.getEntityTypes().contains(getEntityType(trigger.getMsg()));
} }
@Override @Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
TbMsg msg = trigger.getMsg(); TbMsg msg = trigger.getMsg();
String msgType = msg.getType(); String msgType = msg.getType();
ActionType actionType = msgType.equals(DataConstants.ENTITY_CREATED) ? ActionType.ADDED : ActionType actionType = msgType.equals(DataConstants.ENTITY_CREATED) ? ActionType.ADDED :
@ -65,8 +67,10 @@ public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRu
.entityId(msg.getOriginator()) .entityId(msg.getOriginator())
.entityName(msg.getMetaData().getValue("entityName")) .entityName(msg.getMetaData().getValue("entityName"))
.actionType(actionType) .actionType(actionType)
.originatorUserId(UUID.fromString(msg.getMetaData().getValue("userId"))) .userId(UUID.fromString(msg.getMetaData().getValue("userId")))
.originatorUserName(msg.getMetaData().getValue("userName")) .userEmail(trigger.getMsg().getMetaData().getValue("userEmail"))
.userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName"))
.userLastName(trigger.getMsg().getMetaData().getValue("userLastName"))
.entityCustomerId(msg.getCustomerId()) .entityCustomerId(msg.getCustomerId())
.build(); .build();
} }

10
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java

@ -15,13 +15,15 @@
*/ */
package org.thingsboard.server.service.notification.rule.trigger; package org.thingsboard.server.service.notification.rule.trigger;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo; import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionTrigger; import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
@ -42,9 +44,9 @@ public class NewPlatformVersionTriggerProcessor implements NotificationRuleTrigg
} }
@Override @Override
public NotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger) {
return NewPlatformVersionNotificationInfo.builder() return NewPlatformVersionNotificationInfo.builder()
.message(trigger.getMessage().getMessage()) .message(JacksonUtil.convertValue(trigger.getMessage(), new TypeReference<>() {}))
.build(); .build();
} }

6
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java

@ -15,8 +15,8 @@
*/ */
package org.thingsboard.server.service.notification.rule.trigger; package org.thingsboard.server.service.notification.rule.trigger;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger; import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
@ -28,7 +28,7 @@ public interface NotificationRuleTriggerProcessor<T extends NotificationRuleTrig
return false; return false;
} }
NotificationInfo constructNotificationInfo(T trigger, C triggerConfig); RuleOriginatedNotificationInfo constructNotificationInfo(T trigger);
NotificationRuleTriggerType getTriggerType(); NotificationRuleTriggerType getTriggerType();

15
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java

@ -15,25 +15,31 @@
*/ */
package org.thingsboard.server.service.notification.rule.trigger; package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleEngineComponentLifecycleEventNotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleEngineComponentLifecycleEventNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.dao.notification.trigger.RuleEngineComponentLifecycleEventTrigger; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.notification.trigger.RuleEngineComponentLifecycleEventTrigger;
import org.thingsboard.server.queue.discovery.PartitionService;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Set; import java.util.Set;
@Service @Service
@RequiredArgsConstructor
public class RuleEngineComponentLifecycleEventTriggerProcessor implements NotificationRuleTriggerProcessor<RuleEngineComponentLifecycleEventTrigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig> { public class RuleEngineComponentLifecycleEventTriggerProcessor implements NotificationRuleTriggerProcessor<RuleEngineComponentLifecycleEventTrigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig> {
private final PartitionService partitionService;
@Override @Override
public boolean matchesFilter(RuleEngineComponentLifecycleEventTrigger trigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(RuleEngineComponentLifecycleEventTrigger trigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig) {
if (CollectionUtils.isNotEmpty(triggerConfig.getRuleChains())) { if (CollectionUtils.isNotEmpty(triggerConfig.getRuleChains())) {
@ -41,6 +47,9 @@ public class RuleEngineComponentLifecycleEventTriggerProcessor implements Notifi
return false; return false;
} }
} }
if (!partitionService.resolve(ServiceType.TB_RULE_ENGINE, trigger.getTenantId(), trigger.getComponentId()).isMyPartition()) {
return false;
}
EntityType componentType = trigger.getComponentId().getEntityType(); EntityType componentType = trigger.getComponentId().getEntityType();
Set<ComponentLifecycleEvent> trackedEvents; Set<ComponentLifecycleEvent> trackedEvents;
@ -68,7 +77,7 @@ public class RuleEngineComponentLifecycleEventTriggerProcessor implements Notifi
} }
@Override @Override
public NotificationInfo constructNotificationInfo(RuleEngineComponentLifecycleEventTrigger trigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineComponentLifecycleEventTrigger trigger) {
return RuleEngineComponentLifecycleEventNotificationInfo.builder() return RuleEngineComponentLifecycleEventNotificationInfo.builder()
.ruleChainId(trigger.getRuleChainId()) .ruleChainId(trigger.getRuleChainId())
.ruleChainName(trigger.getRuleChainName()) .ruleChainName(trigger.getRuleChainName())

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineMsgNotificationRuleTriggerProcessor.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.notification.rule.trigger; package org.thingsboard.server.service.notification.rule.trigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import java.util.Set; import java.util.Set;

6
application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmCountQuery;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate; import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
@ -205,6 +206,11 @@ public class DefaultEntityQueryService implements EntityQueryService {
} }
} }
@Override
public long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query) {
return alarmService.countAlarmsByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query);
}
private EntityDataQuery buildEntityDataQuery(AlarmDataQuery query) { private EntityDataQuery buildEntityDataQuery(AlarmDataQuery query) {
EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder(); EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder();
EntityDataSortOrder entitiesSortOrder; EntityDataSortOrder entitiesSortOrder;

3
application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java

@ -19,6 +19,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmCountQuery;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityCountQuery;
@ -34,6 +35,8 @@ public interface EntityQueryService {
PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query); PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query);
long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query);
DeferredResult<ResponseEntity> getKeysByQuery(SecurityUser securityUser, TenantId tenantId, EntityDataQuery query, DeferredResult<ResponseEntity> getKeysByQuery(SecurityUser securityUser, TenantId tenantId, EntityDataQuery query,
boolean isTimeseries, boolean isAttributes); boolean isTimeseries, boolean isAttributes);

10
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -35,10 +35,12 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
@ -129,6 +131,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
private final OtaPackageStateService firmwareStateService; private final OtaPackageStateService firmwareStateService;
private final GitVersionControlQueueService vcQueueService; private final GitVersionControlQueueService vcQueueService;
private final NotificationSchedulerService notificationSchedulerService; private final NotificationSchedulerService notificationSchedulerService;
private final NotificationRuleProcessor notificationRuleProcessor;
private final TbCoreConsumerStats stats; private final TbCoreConsumerStats stats;
protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer; protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer;
private final TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> firmwareStatesConsumer; private final TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> firmwareStatesConsumer;
@ -156,7 +159,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
PartitionService partitionService, PartitionService partitionService,
ApplicationEventPublisher eventPublisher, ApplicationEventPublisher eventPublisher,
Optional<JwtSettingsService> jwtSettingsService, Optional<JwtSettingsService> jwtSettingsService,
NotificationSchedulerService notificationSchedulerService) { NotificationSchedulerService notificationSchedulerService,
NotificationRuleProcessor notificationRuleProcessor) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
@ -171,6 +175,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
this.firmwareStateService = firmwareStateService; this.firmwareStateService = firmwareStateService;
this.vcQueueService = vcQueueService; this.vcQueueService = vcQueueService;
this.notificationSchedulerService = notificationSchedulerService; this.notificationSchedulerService = notificationSchedulerService;
this.notificationRuleProcessor = notificationRuleProcessor;
} }
@PostConstruct @PostConstruct
@ -269,6 +274,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg(); TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg();
log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg()); log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg());
forwardToNotificationSchedulerService(notificationSchedulerServiceMsg, callback); forwardToNotificationSchedulerService(notificationSchedulerServiceMsg, callback);
} else if (toCoreMsg.hasNotificationRuleProcessorMsg()) {
Optional<NotificationRuleTrigger> notificationRuleTrigger = encodingService.decode(toCoreMsg.getNotificationRuleProcessorMsg().getTrigger().toByteArray());
notificationRuleTrigger.ifPresent(notificationRuleProcessor::process);
} }
} catch (Throwable e) { } catch (Throwable e) {
log.warn("[{}] Failed to process message: {}", id, msg, e); log.warn("[{}] Failed to process message: {}", id, msg, e);

19
application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java

@ -44,6 +44,8 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultSlackService implements SlackService { public class DefaultSlackService implements SlackService {
@ -80,7 +82,14 @@ public class DefaultSlackService implements SlackService {
.map(user -> { .map(user -> {
SlackConversation conversation = new SlackConversation(); SlackConversation conversation = new SlackConversation();
conversation.setId(user.getId()); conversation.setId(user.getId());
conversation.setName(String.format("@%s (%s)", user.getName(), user.getRealName())); conversation.setShortName(user.getName());
conversation.setWholeName(user.getProfile() != null ? user.getProfile().getRealNameNormalized() : user.getRealName());
conversation.setEmail(user.getProfile() != null ? user.getProfile().getEmail() : null);
String title = "@" + conversation.getShortName();
if (isNotEmpty(conversation.getWholeName()) && !conversation.getWholeName().equals(conversation.getShortName())) {
title += " (" + conversation.getWholeName() + ")";
}
conversation.setTitle(title);
return conversation; return conversation;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -99,7 +108,9 @@ public class DefaultSlackService implements SlackService {
.map(channel -> { .map(channel -> {
SlackConversation conversation = new SlackConversation(); SlackConversation conversation = new SlackConversation();
conversation.setId(channel.getId()); conversation.setId(channel.getId());
conversation.setName("#" + channel.getName()); conversation.setShortName(channel.getName());
conversation.setWholeName(channel.getNameNormalized());
conversation.setTitle("#" + channel.getName());
return conversation; return conversation;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -111,7 +122,7 @@ public class DefaultSlackService implements SlackService {
public SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern) { public SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern) {
List<SlackConversation> conversations = listConversations(tenantId, token, conversationType); List<SlackConversation> conversations = listConversations(tenantId, token, conversationType);
return conversations.stream() return conversations.stream()
.filter(conversation -> StringUtils.containsIgnoreCase(conversation.getName(), namePattern)) .filter(conversation -> StringUtils.containsIgnoreCase(conversation.getTitle(), namePattern))
.findFirst().orElse(null); .findFirst().orElse(null);
} }
@ -144,7 +155,7 @@ public class DefaultSlackService implements SlackService {
String neededScope = response.getNeeded(); String neededScope = response.getNeeded();
error = "bot token scope '" + neededScope + "' is needed"; error = "bot token scope '" + neededScope + "' is needed";
} }
throw new RuntimeException("Failed to send message via Slack: " + error); throw new RuntimeException("Slack API error: " + error);
} }
return response; return response;

35
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -316,20 +316,11 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
Set<TbSubscription> subscriptions = subscriptionsByEntityId.get(recipientId); Set<TbSubscription> subscriptions = subscriptionsByEntityId.get(recipientId);
if (subscriptions != null) { if (subscriptions != null) {
NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationUpdate); NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationUpdate);
log.trace("Handling notificationUpdate for user {}: {}", recipientId, notificationUpdate);
subscriptions.stream() subscriptions.stream()
.filter(subscription -> subscription.getType() == TbSubscriptionType.NOTIFICATIONS .filter(subscription -> subscription.getType() == TbSubscriptionType.NOTIFICATIONS
|| subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT) || subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT)
.forEach(subscription -> { .forEach(subscription -> onNotificationsSubUpdate(subscriptionUpdate, subscription));
if (serviceId.equals(subscription.getServiceId())) {
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(),
subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY);
} else {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, subscription.getServiceId());
ToCoreNotificationMsg updateProto = TbSubscriptionUtils.notificationsSubUpdateToProto(subscription, subscriptionUpdate);
TbProtoQueueMsg<ToCoreNotificationMsg> queueMsg = new TbProtoQueueMsg<>(subscription.getEntityId().getId(), updateProto);
toCoreNotificationsProducer.send(tpi, queueMsg, null);
}
});
} }
callback.onSuccess(); callback.onSuccess();
} }
@ -341,21 +332,37 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
if (entityId.getEntityType() != EntityType.USER) { if (entityId.getEntityType() != EntityType.USER) {
return; return;
} }
log.trace("Handling notificationRequestUpdate for user {}: {}", entityId, notificationRequestUpdate);
subscriptions.forEach(subscription -> { subscriptions.forEach(subscription -> {
if (subscription.getType() != TbSubscriptionType.NOTIFICATIONS && if (subscription.getType() != TbSubscriptionType.NOTIFICATIONS &&
subscription.getType() != TbSubscriptionType.NOTIFICATIONS_COUNT) { subscription.getType() != TbSubscriptionType.NOTIFICATIONS_COUNT) {
return; return;
} }
if (!subscription.getTenantId().equals(tenantId) || !subscription.getServiceId().equals(serviceId)) { if (!subscription.getTenantId().equals(tenantId)) {
return; return;
} }
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(), onNotificationsSubUpdate(subscriptionUpdate, subscription);
subscriptionUpdate, TbCallback.EMPTY);
}); });
}); });
callback.onSuccess(); callback.onSuccess();
} }
private void onNotificationsSubUpdate(NotificationsSubscriptionUpdate subscriptionUpdate, TbSubscription subscription) {
if (serviceId.equals(subscription.getServiceId())) {
log.trace("[{}][{}][{}] Subscription session is managed by current service, forwarding to localSubscriptionService (update: {})",
subscription.getServiceId(), subscription.getEntityId(), subscription.getSessionId(), subscriptionUpdate);
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(),
subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY);
} else {
log.trace("[{}][{}][{}] Subscription session is not managed by current service (update: {})",
subscription.getServiceId(), subscription.getEntityId(), subscription.getSessionId(), subscriptionUpdate);
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, subscription.getServiceId());
ToCoreNotificationMsg updateProto = TbSubscriptionUtils.notificationsSubUpdateToProto(subscription, subscriptionUpdate);
TbProtoQueueMsg<ToCoreNotificationMsg> queueMsg = new TbProtoQueueMsg<>(subscription.getEntityId().getId(), updateProto);
toCoreNotificationsProducer.send(tpi, queueMsg, null);
}
}
@Override @Override
public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, TbCallback callback) { public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, TbCallback callback) {
onLocalTelemetrySubUpdate(entityId, onLocalTelemetrySubUpdate(entityId,

34
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -54,6 +54,7 @@ import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggHistoryCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggKey; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggKey;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggTimeSeriesCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggTimeSeriesCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
@ -94,7 +95,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private static final int DEFAULT_LIMIT = 100; private static final int DEFAULT_LIMIT = 100;
private final Map<String, Map<Integer, TbAbstractSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>(); private final Map<String, Map<Integer, TbAbstractSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>();
@Autowired @Autowired @Lazy
private WebSocketService wsService; private WebSocketService wsService;
@Autowired @Autowired
@ -408,6 +409,26 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
} }
} }
@Override
public void handleCmd(WebSocketSessionRef session, AlarmCountCmd cmd) {
TbAlarmCountSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
if (ctx == null) {
ctx = createSubCtx(session, cmd);
long start = System.currentTimeMillis();
ctx.fetchData();
long end = System.currentTimeMillis();
stats.getAlarmQueryInvocationCnt().incrementAndGet();
stats.getAlarmQueryTimeSpent().addAndGet(end - start);
TbAlarmCountSubCtx finalCtx = ctx;
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
() -> refreshDynamicQuery(finalCtx),
dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx.setRefreshTask(task);
} else {
log.debug("[{}][{}] Received duplicate command: {}", session.getSessionId(), cmd.getCmdId(), cmd);
}
}
private boolean validate(TbAbstractSubCtx<?> finalCtx) { private boolean validate(TbAbstractSubCtx<?> finalCtx) {
if (finalCtx.isStopped()) { if (finalCtx.isStopped()) {
log.warn("[{}][{}][{}] Received validation task for already stopped context.", finalCtx.getTenantId(), finalCtx.getSessionId(), finalCtx.getCmdId()); log.warn("[{}][{}][{}] Received validation task for already stopped context.", finalCtx.getTenantId(), finalCtx.getSessionId(), finalCtx.getCmdId());
@ -501,6 +522,17 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
return ctx; return ctx;
} }
private TbAlarmCountSubCtx createSubCtx(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbAlarmCountSubCtx ctx = new TbAlarmCountSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, alarmService, sessionRef, cmd.getCmdId());
if (cmd.getQuery() != null) {
ctx.setAndResolveQuery(cmd.getQuery());
}
sessionSubs.put(cmd.getCmdId(), ctx);
return ctx;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T extends TbAbstractSubCtx> T getSubCtx(String sessionId, int cmdId) { private <T extends TbAbstractSubCtx> T getSubCtx(String sessionId, int cmdId) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId); Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId);

67
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java

@ -0,0 +1,67 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.subscription;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.query.AlarmCountQuery;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate;
@Slf4j
@ToString(callSuper = true)
public class TbAlarmCountSubCtx extends TbAbstractSubCtx<AlarmCountQuery> {
private final AlarmService alarmService;
@Getter
@Setter
private volatile int result;
public TbAlarmCountSubCtx(String serviceId, WebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
AttributesService attributesService, SubscriptionServiceStatistics stats, AlarmService alarmService,
WebSocketSessionRef sessionRef, int cmdId) {
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
this.alarmService = alarmService;
}
@Override
public void fetchData() {
result = (int) alarmService.countAlarmsByQuery(getTenantId(), getCustomerId(), query);
sendWsMsg(new AlarmCountUpdate(cmdId, result));
}
@Override
protected void update() {
int newCount = (int) alarmService.countAlarmsByQuery(getTenantId(), getCustomerId(), query);
if (newCount != result) {
result = newCount;
sendWsMsg(new AlarmCountUpdate(cmdId, result));
}
}
@Override
public boolean isDynamic() {
return true;
}
}

3
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.subscription; package org.thingsboard.server.service.subscription;
import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd;
@ -29,6 +30,8 @@ public interface TbEntityDataSubscriptionService {
void handleCmd(WebSocketSessionRef sessionId, AlarmDataCmd cmd); void handleCmd(WebSocketSessionRef sessionId, AlarmDataCmd cmd);
void handleCmd(WebSocketSessionRef sessionId, AlarmCountCmd cmd);
void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId); void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId);
void cancelAllSessionSubscriptions(String sessionId); void cancelAllSessionSubscriptions(String sessionId);

34
application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java

@ -30,7 +30,6 @@ import org.thingsboard.server.common.data.SystemInfo;
import org.thingsboard.server.common.data.SystemInfoData; import org.thingsboard.server.common.data.SystemInfoData;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
@ -58,10 +57,9 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.thingsboard.common.util.SystemUtil.getCpuUsage; import static org.thingsboard.common.util.SystemUtil.getCpuUsage;
import static org.thingsboard.common.util.SystemUtil.getFreeDiscSpace;
import static org.thingsboard.common.util.SystemUtil.getFreeMemory;
import static org.thingsboard.common.util.SystemUtil.getMemoryUsage; import static org.thingsboard.common.util.SystemUtil.getMemoryUsage;
import static org.thingsboard.common.util.SystemUtil.getTotalCpuUsage; import static org.thingsboard.common.util.SystemUtil.getDiscSpaceUsage;
import static org.thingsboard.common.util.SystemUtil.getCpuCount;
import static org.thingsboard.common.util.SystemUtil.getTotalDiscSpace; import static org.thingsboard.common.util.SystemUtil.getTotalDiscSpace;
import static org.thingsboard.common.util.SystemUtil.getTotalMemory; import static org.thingsboard.common.util.SystemUtil.getTotalMemory;
@ -112,13 +110,11 @@ public class DefaultSystemInfoService extends TbApplicationEventListener<Partiti
public SystemInfo getSystemInfo() { public SystemInfo getSystemInfo() {
SystemInfo systemInfo = new SystemInfo(); SystemInfo systemInfo = new SystemInfo();
ServiceInfo serviceInfo = serviceInfoProvider.getServiceInfoWithCurrentSystemInfo();
if (discoveryService.isMonolith()) { if (discoveryService.isMonolith()) {
systemInfo.setMonolith(true); systemInfo.setMonolith(true);
systemInfo.setSystemData(Collections.singletonList(createSystemInfoData(serviceInfo))); systemInfo.setSystemData(Collections.singletonList(createSystemInfoData(serviceInfoProvider.generateNewServiceInfoWithCurrentSystemInfo())));
} else { } else {
systemInfo.setSystemData(getSystemData(serviceInfo)); systemInfo.setSystemData(getSystemData(serviceInfoProvider.getServiceInfo()));
} }
return systemInfo; return systemInfo;
@ -156,7 +152,7 @@ public class DefaultSystemInfoService extends TbApplicationEventListener<Partiti
private void saveCurrentClusterSystemInfo() { private void saveCurrentClusterSystemInfo() {
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
List<SystemInfoData> clusterSystemData = getSystemData(serviceInfoProvider.getServiceInfoWithCurrentSystemInfo()); List<SystemInfoData> clusterSystemData = getSystemData(serviceInfoProvider.getServiceInfo());
BasicTsKvEntry clusterDataKv = new BasicTsKvEntry(ts, new JsonDataEntry("clusterSystemData", JacksonUtil.toString(clusterSystemData))); BasicTsKvEntry clusterDataKv = new BasicTsKvEntry(ts, new JsonDataEntry("clusterSystemData", JacksonUtil.toString(clusterSystemData)));
doSave(Collections.singletonList(clusterDataKv)); doSave(Collections.singletonList(clusterDataKv));
} }
@ -165,12 +161,12 @@ public class DefaultSystemInfoService extends TbApplicationEventListener<Partiti
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
List<TsKvEntry> tsList = new ArrayList<>(); List<TsKvEntry> tsList = new ArrayList<>();
getMemoryUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", v)))); getCpuUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuUsage", (long) v))));
getMemoryUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", (long) v))));
getDiscSpaceUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("discUsage", (long) v))));
getCpuCount().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuCount", (long) v))));
getTotalMemory().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("totalMemory", v)))); getTotalMemory().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("totalMemory", v))));
getFreeMemory().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("freeMemory", v))));
getCpuUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new DoubleDataEntry("cpuUsage", v))));
getTotalCpuUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new DoubleDataEntry("totalCpuUsage", v))));
getFreeDiscSpace().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("freeDiscSpace", v))));
getTotalDiscSpace().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("totalDiscSpace", v)))); getTotalDiscSpace().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("totalDiscSpace", v))));
doSave(tsList); doSave(tsList);
@ -196,13 +192,15 @@ public class DefaultSystemInfoService extends TbApplicationEventListener<Partiti
SystemInfoData infoData = new SystemInfoData(); SystemInfoData infoData = new SystemInfoData();
infoData.setServiceId(serviceInfo.getServiceId()); infoData.setServiceId(serviceInfo.getServiceId());
infoData.setServiceType(serviceTypes.size() > 1 ? "MONOLITH" : serviceTypes.get(0)); infoData.setServiceType(serviceTypes.size() > 1 ? "MONOLITH" : serviceTypes.get(0));
infoData.setCpuUsage(serviceInfo.getSystemInfo().getCpuUsage());
infoData.setMemoryUsage(serviceInfo.getSystemInfo().getMemoryUsage()); infoData.setMemoryUsage(serviceInfo.getSystemInfo().getMemoryUsage());
infoData.setDiscUsage(serviceInfo.getSystemInfo().getDiskUsage());
infoData.setCpuCount(serviceInfo.getSystemInfo().getCpuCount());
infoData.setTotalMemory(serviceInfo.getSystemInfo().getTotalMemory()); infoData.setTotalMemory(serviceInfo.getSystemInfo().getTotalMemory());
infoData.setFreeMemory(serviceInfo.getSystemInfo().getFreeMemory());
infoData.setCpuUsage(serviceInfo.getSystemInfo().getCpuUsage());
infoData.setTotalCpuUsage(serviceInfo.getSystemInfo().getTotalCpuUsage());
infoData.setFreeDiscSpace(serviceInfo.getSystemInfo().getFreeDiscSpace());
infoData.setTotalDiscSpace(serviceInfo.getSystemInfo().getTotalDiscSpace()); infoData.setTotalDiscSpace(serviceInfo.getSystemInfo().getTotalDiscSpace());
return infoData; return infoData;
} }

14
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java

@ -26,6 +26,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
@ -45,15 +46,14 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.msg.notification.trigger.AlarmTrigger;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService; import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService;
import org.thingsboard.server.dao.notification.trigger.AlarmTrigger;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.Collection; import java.util.Collection;
@ -70,7 +70,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
private final TbAlarmCommentService alarmCommentService; private final TbAlarmCommentService alarmCommentService;
private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageReportClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService; private final TbApiUsageStateService apiUsageStateService;
private final NotificationRuleProcessingService notificationRuleProcessingService; private final NotificationRuleProcessor notificationRuleProcessor;
@Override @Override
protected String getExecutorPrefix() { protected String getExecutorPrefix() {
@ -235,7 +235,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm);
}); });
} }
notificationRuleProcessingService.process(tenantId, AlarmTrigger.builder() notificationRuleProcessor.process(AlarmTrigger.builder()
.tenantId(tenantId)
.alarmUpdate(result) .alarmUpdate(result)
.build()); .build());
}); });
@ -252,7 +253,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm); return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm);
}); });
} }
notificationRuleProcessingService.process(tenantId, AlarmTrigger.builder() notificationRuleProcessor.process(AlarmTrigger.builder()
.tenantId(tenantId)
.alarmUpdate(result) .alarmUpdate(result)
.build()); .build());
}); });

27
application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java

@ -21,13 +21,13 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.info.BuildProperties;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionTrigger; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -57,8 +57,11 @@ public class DefaultUpdateService implements UpdateService {
@Value("${updates.enabled}") @Value("${updates.enabled}")
private boolean updatesEnabled; private boolean updatesEnabled;
@Autowired(required = false)
private BuildProperties buildProperties;
@Autowired @Autowired
private NotificationRuleProcessingService notificationRuleProcessingService; private NotificationRuleProcessor notificationRuleProcessor;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service")); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service"));
@ -73,14 +76,11 @@ public class DefaultUpdateService implements UpdateService {
@PostConstruct @PostConstruct
private void init() { private void init() {
updateMessage = new UpdateMessage("", false, ""); version = buildProperties != null ? buildProperties.getVersion() : "unknown";
updateMessage = new UpdateMessage(false, version, "", "", "", "");
if (updatesEnabled) { if (updatesEnabled) {
try { try {
platform = System.getProperty("platform", "unknown"); platform = System.getProperty("platform", "unknown");
version = getClass().getPackage().getImplementationVersion();
if (version == null) {
version = "unknown";
}
instanceId = parseInstanceId(); instanceId = parseInstanceId();
checkUpdatesFuture = scheduler.scheduleAtFixedRate(checkUpdatesRunnable, 0, 1, TimeUnit.HOURS); checkUpdatesFuture = scheduler.scheduleAtFixedRate(checkUpdatesRunnable, 0, 1, TimeUnit.HOURS);
} catch (Exception e) { } catch (Exception e) {
@ -128,15 +128,10 @@ public class DefaultUpdateService implements UpdateService {
request.put(PLATFORM_PARAM, platform); request.put(PLATFORM_PARAM, platform);
request.put(VERSION_PARAM, version); request.put(VERSION_PARAM, version);
request.put(INSTANCE_ID_PARAM, instanceId.toString()); request.put(INSTANCE_ID_PARAM, instanceId.toString());
JsonNode response = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/thingsboard/updates", request, JsonNode.class);
UpdateMessage prevUpdateMessage = updateMessage; UpdateMessage prevUpdateMessage = updateMessage;
updateMessage = new UpdateMessage( updateMessage = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v2/thingsboard/updates", request, UpdateMessage.class);
response.get("message").asText(),
response.get("updateAvailable").asBoolean(),
version
);
if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) { if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) {
notificationRuleProcessingService.process(TenantId.SYS_TENANT_ID, NewPlatformVersionTrigger.builder() notificationRuleProcessor.process(NewPlatformVersionTrigger.builder()
.message(updateMessage) .message(updateMessage)
.build()); .build());
} }

26
application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java

@ -72,6 +72,7 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.SubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.SubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.TelemetryPluginCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.TelemetryPluginCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
@ -166,10 +167,11 @@ public class DefaultWebSocketService implements WebSocketService {
newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd), newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd),
newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd), newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd),
newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd), newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd),
newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmCountCmds, this::handleWsAlarmCountCmd),
newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd),
newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd),
newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd),
newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd) newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd)
); );
notificationCmdsHandlers = List.of( notificationCmdsHandlers = List.of(
newCmdHandler(NotificationCmdsWrapper::getUnreadSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd), newCmdHandler(NotificationCmdsWrapper::getUnreadSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd),
@ -302,6 +304,16 @@ public class DefaultWebSocketService implements WebSocketService {
} }
} }
private void handleWsAlarmCountCmd(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) {
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)
&& validateSubscriptionCmd(sessionRef, cmd)) {
entityDataSubService.handleCmd(sessionRef, cmd);
}
}
@Override @Override
public void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update) { public void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update) {
sendWsMsg(sessionId, update.getSubscriptionId(), update); sendWsMsg(sessionId, update.getSubscriptionId(), update);
@ -866,6 +878,16 @@ public class DefaultWebSocketService implements WebSocketService {
} }
} }
private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) {
if (cmd.getCmdId() < 0) {
TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
"Cmd id is negative value!");
sendWsMsg(sessionRef, update);
return false;
}
return true;
}
private void sendWsMsg(WebSocketSessionRef sessionRef, EntityDataUpdate update) { private void sendWsMsg(WebSocketSessionRef sessionRef, EntityDataUpdate update) {
sendWsMsg(sessionRef, update.getCmdId(), update); sendWsMsg(sessionRef, update.getCmdId(), update);
} }

26
application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java

@ -120,17 +120,21 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
/* Notifications subscription update handling */ /* Notifications subscription update handling */
private void handleNotificationsSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { private void handleNotificationsSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
if (subscriptionUpdate.getNotificationUpdate() != null) { try {
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate()); if (subscriptionUpdate.getNotificationUpdate() != null) {
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) { handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate()); } else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
}
} catch (Exception e) {
log.error("[{}, subId: {}] Failed to handle update for notifications subscription: {}", subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, e);
} }
} }
private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) { private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) {
log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
Notification notification = update.getNotification(); Notification notification = update.getNotification();
UUID notificationId = update.getNotificationId(); UUID notificationId = notification != null ? notification.getUuidId() : update.getNotificationId();
if (update.isCreated()) { if (update.isCreated()) {
subscription.getLatestUnreadNotifications().put(notificationId, notification); subscription.getLatestUnreadNotifications().put(notificationId, notification);
subscription.getTotalUnreadCounter().incrementAndGet(); subscription.getTotalUnreadCounter().incrementAndGet();
@ -175,10 +179,14 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
/* Notifications count subscription update handling */ /* Notifications count subscription update handling */
private void handleNotificationsCountSubscriptionUpdate(NotificationsCountSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { private void handleNotificationsCountSubscriptionUpdate(NotificationsCountSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
if (subscriptionUpdate.getNotificationUpdate() != null) { try {
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate()); if (subscriptionUpdate.getNotificationUpdate() != null) {
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) { handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate()); } else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
}
} catch (Exception e) {
log.error("[{}, subId: {}] Failed to handle update for notifications count subscription: {}", subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, e);
} }
} }

10
application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java

@ -15,12 +15,10 @@
*/ */
package org.thingsboard.server.service.ws.notification.sub; package org.thingsboard.server.service.ws.notification.sub;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.NotificationStatus;
@ -32,7 +30,7 @@ import java.util.UUID;
@Builder @Builder
public class NotificationUpdate { public class NotificationUpdate {
private NotificationId notificationId; private UUID notificationId;
private boolean created; private boolean created;
private Notification notification; private Notification notification;
@ -43,10 +41,4 @@ public class NotificationUpdate {
private boolean deleted; private boolean deleted;
@JsonIgnore
public UUID getNotificationId() {
return notificationId != null ? notificationId.getId() :
notification != null ? notification.getUuidId() : null;
}
} }

6
application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java

@ -19,6 +19,8 @@ import lombok.Data;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUnsubscribeCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUnsubscribeCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUnsubscribeCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUnsubscribeCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUnsubscribeCmd;
@ -52,4 +54,8 @@ public class TelemetryPluginCmdsWrapper {
private List<EntityCountUnsubscribeCmd> entityCountUnsubscribeCmds; private List<EntityCountUnsubscribeCmd> entityCountUnsubscribeCmds;
private List<AlarmCountCmd> alarmCountCmds;
private List<AlarmCountUnsubscribeCmd> alarmCountUnsubscribeCmds;
} }

34
application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.ws.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.thingsboard.server.common.data.query.AlarmCountQuery;
public class AlarmCountCmd extends DataCmd {
@Getter
private final AlarmCountQuery query;
@JsonCreator
public AlarmCountCmd(@JsonProperty("cmdId") int cmdId,
@JsonProperty("query") AlarmCountQuery query) {
super(cmdId);
this.query = query;
}
}

10
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java → application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java

@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.common.data; package org.thingsboard.server.service.ws.telemetry.cmd.v2;
import lombok.Data; import lombok.Data;
@Data @Data
public class ApiUsageStateMailMessage { public class AlarmCountUnsubscribeCmd implements UnsubscribeCmd {
private final ApiUsageRecordKey key;
private final long threshold; private final int cmdId;
private final long value;
} }

53
application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUpdate.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.ws.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.service.subscription.SubscriptionErrorCode;
@ToString
public class AlarmCountUpdate extends CmdUpdate {
@Getter
private int count;
public AlarmCountUpdate(int cmdId, int count) {
super(cmdId, SubscriptionErrorCode.NO_ERROR.getCode(), null);
this.count = count;
}
public AlarmCountUpdate(int cmdId, int errorCode, String errorMsg) {
super(cmdId, errorCode, errorMsg);
}
@Override
public CmdUpdateType getCmdUpdateType() {
return CmdUpdateType.ALARM_COUNT_DATA;
}
@JsonCreator
public AlarmCountUpdate(@JsonProperty("cmdId") int cmdId,
@JsonProperty("count") int count,
@JsonProperty("errorCode") int errorCode,
@JsonProperty("errorMsg") String errorMsg) {
super(cmdId, errorCode, errorMsg);
this.count = count;
}
}

1
application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v2;
public enum CmdUpdateType { public enum CmdUpdateType {
ENTITY_DATA, ENTITY_DATA,
ALARM_DATA, ALARM_DATA,
ALARM_COUNT_DATA,
COUNT_DATA, COUNT_DATA,
NOTIFICATIONS, NOTIFICATIONS,
NOTIFICATIONS_COUNT NOTIFICATIONS_COUNT

6
application/src/main/resources/thingsboard.yml

@ -474,6 +474,12 @@ cache:
userSettings: userSettings:
timeToLiveInMinutes: "${CACHE_SPECS_USER_SETTINGS_TTL:1440}" timeToLiveInMinutes: "${CACHE_SPECS_USER_SETTINGS_TTL:1440}"
maxSize: "${CACHE_SPECS_USER_SETTINGS_MAX_SIZE:100000}" maxSize: "${CACHE_SPECS_USER_SETTINGS_MAX_SIZE:100000}"
dashboardTitles:
timeToLiveInMinutes: "${CACHE_SPECS_DASHBOARD_TITLES_TTL:1440}"
maxSize: "${CACHE_SPECS_DASHBOARD_TITLES_MAX_SIZE:100000}"
entityCount:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_COUNT_TTL:1440}"
maxSize: "${CACHE_SPECS_ENTITY_COUNT_MAX_SIZE:100000}"
#Disable this because it is not required. #Disable this because it is not required.
spring.data.redis.repositories.enabled: false spring.data.redis.repositories.enabled: false

149
application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java

@ -29,9 +29,12 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmCountQuery;
import org.thingsboard.server.common.data.query.DeviceTypeFilter; import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType; import org.thingsboard.server.common.data.query.DynamicValueSourceType;
@ -52,6 +55,7 @@ import org.thingsboard.server.common.data.security.Authority;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -141,6 +145,15 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
@Test @Test
public void testSysAdminCountEntitiesByQuery() throws Exception { public void testSysAdminCountEntitiesByQuery() throws Exception {
loginSysAdmin();
EntityTypeFilter allDeviceFilter = new EntityTypeFilter();
allDeviceFilter.setEntityType(EntityType.DEVICE);
EntityCountQuery query = new EntityCountQuery(allDeviceFilter);
Long initialCount = doPostWithResponse("/api/entitiesQuery/count", query, Long.class);
loginTenantAdmin();
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
for (int i = 0; i < 97; i++) { for (int i = 0; i < 97; i++) {
Device device = new Device(); Device device = new Device();
@ -180,13 +193,139 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class); count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
Assert.assertEquals(97, count.longValue()); Assert.assertEquals(97, count.longValue());
EntityTypeFilter filter2 = new EntityTypeFilter(); Long count2 = doPostWithResponse("/api/entitiesQuery/count", query, Long.class);
filter2.setEntityType(EntityType.DEVICE); Assert.assertEquals(initialCount + 97, count2.longValue());
}
EntityCountQuery countQuery2 = new EntityCountQuery(filter2); @Test
public void testTenantCountAlarmsByQuery() throws Exception {
loginTenantAdmin();
List<Device> devices = new ArrayList<>();
List<Alarm> alarms = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Device device = new Device();
device.setName("Device" + i);
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
devices.add(doPost("/api/device", device, Device.class));
Thread.sleep(1);
}
Long count2 = doPostWithResponse("/api/entitiesQuery/count", countQuery2, Long.class); for (int i = 0; i < devices.size(); i++) {
Assert.assertEquals(97, count2.longValue()); Alarm alarm = new Alarm();
alarm.setOriginator(devices.get(i).getId());
alarm.setType("alarm" + i);
alarm.setSeverity(AlarmSeverity.WARNING);
alarms.add(doPost("/api/alarm", alarm, Alarm.class));
Thread.sleep(1);
}
testCountAlarmsByQuery(alarms);
}
@Test
public void testCustomerCountAlarmsByQuery() throws Exception {
loginTenantAdmin();
List<Device> devices = new ArrayList<>();
List<Alarm> alarms = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Device device = new Device();
device.setCustomerId(customerId);
device.setName("Device" + i);
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
devices.add(doPost("/api/device", device, Device.class));
Thread.sleep(1);
}
loginCustomerUser();
for (int i = 0; i < devices.size(); i++) {
Alarm alarm = new Alarm();
alarm.setCustomerId(customerId);
alarm.setOriginator(devices.get(i).getId());
alarm.setType("alarm" + i);
alarm.setSeverity(AlarmSeverity.WARNING);
alarms.add(doPost("/api/alarm", alarm, Alarm.class));
Thread.sleep(1);
}
testCountAlarmsByQuery(alarms);
}
private void testCountAlarmsByQuery(List<Alarm> alarms) throws Exception {
AlarmCountQuery countQuery = new AlarmCountQuery();
Long count = doPostWithResponse("/api/alarmsQuery/count", countQuery, Long.class);
Assert.assertEquals(97, count.longValue());
countQuery = AlarmCountQuery.builder()
.typeList(List.of("unknown"))
.build();
count = doPostWithResponse("/api/alarmsQuery/count", countQuery, Long.class);
Assert.assertEquals(0, count.longValue());
countQuery = AlarmCountQuery.builder()
.typeList(List.of("alarm1", "alarm2", "alarm3"))
.build();
count = doPostWithResponse("/api/alarmsQuery/count", countQuery, Long.class);
Assert.assertEquals(3, count.longValue());
countQuery = AlarmCountQuery.builder()
.typeList(alarms.stream().map(Alarm::getType).collect(Collectors.toList()))
.build();
count = doPostWithResponse("/api/alarmsQuery/count", countQuery, Long.class);
Assert.assertEquals(97, count.longValue());
countQuery = AlarmCountQuery.builder()
.severityList(List.of(AlarmSeverity.CRITICAL))
.build();
count = doPostWithResponse("/api/alarmsQuery/count", countQuery, Long.class);
Assert.assertEquals(0, count.longValue());
countQuery = AlarmCountQuery.builder()
.severityList(List.of(AlarmSeverity.WARNING))
.build();
count = doPostWithResponse("/api/alarmsQuery/count", countQuery, Long.class);
Assert.assertEquals(97, count.longValue());
long startTs = alarms.stream().map(Alarm::getCreatedTime).min(Long::compareTo).get();
long endTs = alarms.stream().map(Alarm::getCreatedTime).max(Long::compareTo).get();
countQuery = AlarmCountQuery.builder()
.startTs(startTs - 1)
.endTs(endTs + 1)
.build();
count = doPostWithResponse("/api/alarmsQuery/count", countQuery, Long.class);
Assert.assertEquals(97, count.longValue());
countQuery = AlarmCountQuery.builder()
.startTs(0)
.endTs(endTs + 1)
.build();
count = doPostWithResponse("/api/alarmsQuery/count", countQuery, Long.class);
Assert.assertEquals(97, count.longValue());
countQuery = AlarmCountQuery.builder()
.startTs(0)
.endTs(System.currentTimeMillis())
.build();
count = doPostWithResponse("/api/alarmsQuery/count", countQuery, Long.class);
Assert.assertEquals(97, count.longValue());
countQuery = AlarmCountQuery.builder()
.startTs(endTs + 1)
.endTs(System.currentTimeMillis())
.build();
count = doPostWithResponse("/api/alarmsQuery/count", countQuery, Long.class);
Assert.assertEquals(0, count.longValue());
} }
@Test @Test

137
application/src/test/java/org/thingsboard/server/controller/BaseHomePageApiTest.java

@ -26,11 +26,13 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.FeaturesInfo; import org.thingsboard.server.common.data.FeaturesInfo;
import org.thingsboard.server.common.data.Tenant; 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.UsageInfo;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -49,7 +51,9 @@ import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityTypeFilter; import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.stats.TbApiUsageStateClient; import org.thingsboard.server.common.stats.TbApiUsageStateClient;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate;
@ -69,10 +73,13 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
@Autowired @Autowired
private TbApiUsageStateClient apiUsageStateClient; private TbApiUsageStateClient apiUsageStateClient;
@Autowired
private TbTenantProfileCache tenantProfileCache;
//For system administrator //For system administrator
@Test @Test
public void testTenantsCountWsCmd() throws Exception { public void testTenantsCountWsCmd() throws Exception {
loginSysAdmin(); Long initialCount = getInitialEntityCount(EntityType.TENANT);
List<Tenant> tenants = new ArrayList<>(); List<Tenant> tenants = new ArrayList<>();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
@ -87,7 +94,7 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
getWsClient().send(cmd); getWsClient().send(cmd);
EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply()); EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply());
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
Assert.assertEquals(101, update.getCount()); Assert.assertEquals(initialCount + 100, update.getCount());
for (Tenant tenant : tenants) { for (Tenant tenant : tenants) {
doDelete("/api/tenant/" + tenant.getId().toString()); doDelete("/api/tenant/" + tenant.getId().toString());
@ -96,7 +103,7 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
@Test @Test
public void testTenantProfilesCountWsCmd() throws Exception { public void testTenantProfilesCountWsCmd() throws Exception {
loginSysAdmin(); Long initialCount = getInitialEntityCount(EntityType.TENANT_PROFILE);
List<TenantProfile> tenantProfiles = new ArrayList<>(); List<TenantProfile> tenantProfiles = new ArrayList<>();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
@ -111,7 +118,7 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
getWsClient().send(cmd); getWsClient().send(cmd);
EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply()); EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply());
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
Assert.assertEquals(101, update.getCount()); Assert.assertEquals(initialCount + 100, update.getCount());
for (TenantProfile tenantProfile : tenantProfiles) { for (TenantProfile tenantProfile : tenantProfiles) {
doDelete("/api/tenantProfile/" + tenantProfile.getId().toString()); doDelete("/api/tenantProfile/" + tenantProfile.getId().toString());
@ -120,7 +127,7 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
@Test @Test
public void testUsersCountWsCmd() throws Exception { public void testUsersCountWsCmd() throws Exception {
loginSysAdmin(); Long initialCount = getInitialEntityCount(EntityType.USER);
List<User> users = new ArrayList<>(); List<User> users = new ArrayList<>();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
@ -137,7 +144,7 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
getWsClient().send(cmd); getWsClient().send(cmd);
EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply()); EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply());
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
Assert.assertEquals(103, update.getCount()); Assert.assertEquals(initialCount + 100, update.getCount());
for (User user : users) { for (User user : users) {
doDelete("/api/user/" + user.getId().toString()); doDelete("/api/user/" + user.getId().toString());
@ -146,6 +153,7 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
@Test @Test
public void testCustomersCountWsCmd() throws Exception { public void testCustomersCountWsCmd() throws Exception {
Long initialCount = getInitialEntityCount(EntityType.CUSTOMER);
loginTenantAdmin(); loginTenantAdmin();
List<Customer> customers = new ArrayList<>(); List<Customer> customers = new ArrayList<>();
@ -162,7 +170,7 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
getWsClient().send(cmd); getWsClient().send(cmd);
EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply()); EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply());
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
Assert.assertEquals(101, update.getCount()); Assert.assertEquals(initialCount + 100, update.getCount());
loginTenantAdmin(); loginTenantAdmin();
for (Customer customer : customers) { for (Customer customer : customers) {
@ -172,6 +180,7 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
@Test @Test
public void testDevicesCountWsCmd() throws Exception { public void testDevicesCountWsCmd() throws Exception {
Long initialCount = getInitialEntityCount(EntityType.DEVICE);
loginTenantAdmin(); loginTenantAdmin();
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
@ -188,7 +197,7 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
getWsClient().send(cmd); getWsClient().send(cmd);
EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply()); EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply());
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
Assert.assertEquals(100, update.getCount()); Assert.assertEquals(initialCount + 100, update.getCount());
loginTenantAdmin(); loginTenantAdmin();
for (Device device : devices) { for (Device device : devices) {
@ -198,6 +207,7 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
@Test @Test
public void testAssetsCountWsCmd() throws Exception { public void testAssetsCountWsCmd() throws Exception {
Long initialCount = getInitialEntityCount(EntityType.ASSET);
loginTenantAdmin(); loginTenantAdmin();
List<Asset> assets = new ArrayList<>(); List<Asset> assets = new ArrayList<>();
@ -214,7 +224,7 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
getWsClient().send(cmd); getWsClient().send(cmd);
EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply()); EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply());
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
Assert.assertEquals(100, update.getCount()); Assert.assertEquals(initialCount + 100, update.getCount());
loginTenantAdmin(); loginTenantAdmin();
for (Asset asset : assets) { for (Asset asset : assets) {
@ -238,18 +248,19 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(apiUsageState.getId(), pageData.getData().get(0).getEntityId()); Assert.assertEquals(apiUsageState.getId(), pageData.getData().get(0).getEntityId());
List<String> metrics = List.of("cpuUsage", "memoryUsage", "discUsage", "cpuCount", "totalMemory", "totalDiscSpace");
update = getWsClient().subscribeTsUpdate( update = getWsClient().subscribeTsUpdate(
List.of("memoryUsage", "totalMemory", "freeMemory", "cpuUsage", "totalCpuUsage", "freeDiscSpace", "totalDiscSpace"), metrics,
now, TimeUnit.HOURS.toMillis(1)); now, TimeUnit.HOURS.toMillis(1));
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
List<EntityData> listData = update.getUpdate(); List<EntityData> listData = update.getUpdate();
Assert.assertNotNull(listData); Assert.assertNotNull(listData);
Assert.assertEquals(1, listData.size()); Assert.assertEquals(1, listData.size());
Assert.assertEquals(apiUsageState.getId(), listData.get(0).getEntityId()); Assert.assertEquals(apiUsageState.getId(), listData.get(0).getEntityId());
Assert.assertEquals(7, listData.get(0).getTimeseries().size()); Assert.assertEquals(metrics.size(), listData.get(0).getTimeseries().size());
for (TsValue[] tsv : listData.get(0).getTimeseries().values()) { for (TsValue[] tsv : listData.get(0).getTimeseries().values()) {
Assert.assertTrue(tsv.length > 1); Assert.assertTrue(tsv.length > 0);
} }
} }
@ -329,6 +340,108 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
Assert.assertTrue(featuresInfo.isOauthEnabled()); Assert.assertTrue(featuresInfo.isOauthEnabled());
} }
@Test
public void testUsageInfo() throws Exception {
loginTenantAdmin();
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
Assert.assertNotNull(tenantProfile);
DefaultTenantProfileConfiguration configuration = (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration();
UsageInfo usageInfo = doGet("/api/usage", UsageInfo.class);
Assert.assertNotNull(usageInfo);
Assert.assertEquals(0, usageInfo.getDevices());
Assert.assertEquals(configuration.getMaxDevices(), usageInfo.getMaxDevices());
Assert.assertEquals(0, usageInfo.getAssets());
Assert.assertEquals(configuration.getMaxAssets(), usageInfo.getMaxAssets());
Assert.assertEquals(1, usageInfo.getCustomers());
Assert.assertEquals(configuration.getMaxCustomers(), usageInfo.getMaxCustomers());
Assert.assertEquals(2, usageInfo.getUsers());
Assert.assertEquals(configuration.getMaxUsers(), usageInfo.getMaxUsers());
Assert.assertEquals(0, usageInfo.getDashboards());
Assert.assertEquals(configuration.getMaxDashboards(), usageInfo.getMaxDashboards());
Assert.assertEquals(0, usageInfo.getTransportMessages());
Assert.assertEquals(configuration.getMaxTransportMessages(), usageInfo.getMaxTransportMessages());
Assert.assertEquals(0, usageInfo.getJsExecutions());
Assert.assertEquals(configuration.getMaxJSExecutions(), usageInfo.getMaxJsExecutions());
Assert.assertEquals(0, usageInfo.getEmails());
Assert.assertEquals(configuration.getMaxEmails(), usageInfo.getMaxEmails());
Assert.assertEquals(0, usageInfo.getSms());
Assert.assertEquals(configuration.getMaxSms(), usageInfo.getMaxSms());
Assert.assertEquals(0, usageInfo.getAlarms());
Assert.assertEquals(configuration.getMaxCreatedAlarms(), usageInfo.getMaxAlarms());
List<Device> devices = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Device device = new Device();
device.setName("device" + i);
devices.add(doPost("/api/device", device, Device.class));
}
usageInfo = doGet("/api/usage", UsageInfo.class);
Assert.assertEquals(devices.size(), usageInfo.getDevices());
List<Asset> assets = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Asset asset = new Asset();
asset.setName("asset" + i);
assets.add(doPost("/api/asset", asset, Asset.class));
}
usageInfo = doGet("/api/usage", UsageInfo.class);
Assert.assertEquals(assets.size(), usageInfo.getAssets());
List<Customer> customers = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Customer customer = new Customer();
customer.setTitle("customer" + i);
customers.add(doPost("/api/customer", customer, Customer.class));
}
usageInfo = doGet("/api/usage", UsageInfo.class);
Assert.assertEquals(customers.size() + 1, usageInfo.getCustomers());
List<User> users = new ArrayList<>();
for (int i = 0; i < 97; i++) {
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setEmail(i + "user@thingsboard.org");
users.add(doPost("/api/user", user, User.class));
}
usageInfo = doGet("/api/usage", UsageInfo.class);
Assert.assertEquals(users.size() + 2, usageInfo.getUsers());
List<Dashboard> dashboards = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("dashboard" + i);
dashboards.add(doPost("/api/dashboard", dashboard, Dashboard.class));
}
usageInfo = doGet("/api/usage", UsageInfo.class);
Assert.assertEquals(dashboards.size(), usageInfo.getDashboards());
}
private Long getInitialEntityCount(EntityType entityType) throws Exception {
loginSysAdmin();
EntityTypeFilter allEntityFilter = new EntityTypeFilter();
allEntityFilter.setEntityType(entityType);
EntityCountQuery query = new EntityCountQuery(allEntityFilter);
return doPostWithResponse("/api/entitiesQuery/count", query, Long.class);
}
private OAuth2Info createDefaultOAuth2Info() { private OAuth2Info createDefaultOAuth2Info() {
return new OAuth2Info(true, Lists.newArrayList( return new OAuth2Info(true, Lists.newArrayList(

188
application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java

@ -18,6 +18,7 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore; import org.junit.Ignore;
@ -31,6 +32,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
@ -42,6 +44,8 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.settings.StarredDashboardInfo;
import org.thingsboard.server.common.data.settings.UserDashboardsInfo;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.user.UserDao; import org.thingsboard.server.dao.user.UserDao;
@ -356,7 +360,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
String userIdStr = savedUser.getId().getId().toString(); String userIdStr = savedUser.getId().getId().toString();
doGet("/api/user/" + userIdStr) doGet("/api/user/" + userIdStr)
.andExpect(status().isNotFound()) .andExpect(status().isNotFound())
.andExpect(statusReason(containsString( msgErrorNoFound("User",userIdStr)))); .andExpect(statusReason(containsString(msgErrorNoFound("User", userIdStr))));
} }
@Test @Test
@ -574,9 +578,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
String email1 = "testEmail1"; String email1 = "testEmail1";
String email2 = "testEmail2"; String email2 = "testEmail2";
List<User> customerUsersEmail1 = new ArrayList<>(); List<User> customerUsersEmail1 = new ArrayList<>();
List<User> customerUsersEmail2= new ArrayList<>(); List<User> customerUsersEmail2 = new ArrayList<>();
for (int i = 0; i < 45; i++) { for (int i = 0; i < 45; i++) {
User customerUser = createCustomerUser( customerId); User customerUser = createCustomerUser(customerId);
customerUser.setEmail(email1 + StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)) + "@thingsboard.org"); customerUser.setEmail(email1 + StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)) + "@thingsboard.org");
customerUsersEmail1.add(doPost("/api/user", customerUser, User.class)); customerUsersEmail1.add(doPost("/api/user", customerUser, User.class));
@ -685,7 +689,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
JsonNode retrievedSettings = doGet("/api/user/settings", JsonNode.class); JsonNode retrievedSettings = doGet("/api/user/settings", JsonNode.class);
Assert.assertEquals(retrievedSettings, userSettings); Assert.assertEquals(retrievedSettings, userSettings);
} }
@Test @Test
public void testShouldNotSaveJsonWithRestrictedSymbols() throws Exception { public void testShouldNotSaveJsonWithRestrictedSymbols() throws Exception {
@ -860,7 +864,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> expectedUserInfos = customerUsersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(), List<UserEmailInfo> expectedUserInfos = customerUsersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(),
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(), customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName())) customerUser.getLastName() == null ? "" : customerUser.getLastName()))
.sorted(userDataIdComparator).collect(Collectors.toList()); .sorted(userDataIdComparator).collect(Collectors.toList());
usersInfo.sort(userDataIdComparator); usersInfo.sort(userDataIdComparator);
@ -912,8 +916,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> usersInfo = getUsersInfo(pageLink); List<UserEmailInfo> usersInfo = getUsersInfo(pageLink);
List<UserEmailInfo> expectedUserInfos = usersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(), List<UserEmailInfo> expectedUserInfos = usersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(),
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(), customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName())) customerUser.getLastName() == null ? "" : customerUser.getLastName()))
.sorted(userDataIdComparator).collect(Collectors.toList()); .sorted(userDataIdComparator).collect(Collectors.toList());
usersInfo.sort(userDataIdComparator); usersInfo.sort(userDataIdComparator);
@ -922,7 +926,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
// find user by full last name // find user by full last name
pageLink = new PageLink(10, 0, searchText + "3"); pageLink = new PageLink(10, 0, searchText + "3");
usersInfo = getUsersInfo(pageLink); usersInfo = getUsersInfo(pageLink);
Assert.assertEquals(2, usersInfo.size()); Assert.assertEquals(2, usersInfo.size());
//clear users //clear users
doDelete("/api/customer/" + customerId.getId().toString()) doDelete("/api/customer/" + customerId.getId().toString())
@ -941,6 +945,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
private static User createCustomerUser(CustomerId customerId) { private static User createCustomerUser(CustomerId customerId) {
return createCustomerUser(null, null, customerId); return createCustomerUser(null, null, customerId);
} }
private static User createCustomerUser(String firstName, String lastName, CustomerId customerId) { private static User createCustomerUser(String firstName, String lastName, CustomerId customerId) {
String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
return createCustomerUser(firstName, lastName, "testMail" + suffix + "@thingsboard.org", customerId); return createCustomerUser(firstName, lastName, "testMail" + suffix + "@thingsboard.org", customerId);
@ -959,6 +964,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
private User createTenantAdminUser() { private User createTenantAdminUser() {
return createTenantAdminUser(null, null); return createTenantAdminUser(null, null);
} }
private User createTenantAdminUser(String firstName, String lastName) { private User createTenantAdminUser(String firstName, String lastName) {
String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
@ -975,7 +981,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> loadedCustomerUsers = new ArrayList<>(); List<UserEmailInfo> loadedCustomerUsers = new ArrayList<>();
PageData<UserEmailInfo> pageData = null; PageData<UserEmailInfo> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/users/info?", new TypeReference<>() {}, pageLink); pageData = doGetTypedWithPageLink("/api/users/info?", new TypeReference<>() {
}, pageLink);
loadedCustomerUsers.addAll(pageData.getData()); loadedCustomerUsers.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
@ -984,4 +991,167 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
return loadedCustomerUsers; return loadedCustomerUsers;
} }
@Test
public void testEmptyDashboardSettings() throws Exception {
loginCustomerUser();
UserDashboardsInfo retrievedSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class);
Assert.assertNotNull(retrievedSettings);
Assert.assertNotNull(retrievedSettings.getLast());
Assert.assertTrue(retrievedSettings.getLast().isEmpty());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
}
@Test
public void testDashboardSettingsFlow() throws Exception {
loginTenantAdmin();
Dashboard dashboard1 = new Dashboard();
dashboard1.setTitle("My dashboard 1");
Dashboard savedDashboard1 = doPost("/api/dashboard", dashboard1, Dashboard.class);
Dashboard dashboard2 = new Dashboard();
dashboard2.setTitle("My dashboard 2");
Dashboard savedDashboard2 = doPost("/api/dashboard", dashboard2, Dashboard.class);
UserDashboardsInfo retrievedSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class);
Assert.assertNotNull(retrievedSettings);
Assert.assertNotNull(retrievedSettings.getLast());
Assert.assertTrue(retrievedSettings.getLast().isEmpty());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
UserDashboardsInfo newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/visit", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(1, newSettings.getLast().size());
var lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
newSettings = doGet("/api/user/dashboards/" + savedDashboard2.getId().getId() + "/visit", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
newSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/star", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertFalse(lastVisited.isStarred());
lastVisited = newSettings.getLast().get(1);
Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertEquals(1, newSettings.getStarred().size());
StarredDashboardInfo starred = newSettings.getStarred().get(0);
Assert.assertEquals(savedDashboard1.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard1.getTitle(), starred.getTitle());
newSettings = doGet("/api/user/dashboards/" + savedDashboard2.getId().getId() + "/star", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
lastVisited = newSettings.getLast().get(1);
Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertEquals(2, newSettings.getStarred().size());
starred = newSettings.getStarred().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle());
newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/unstar", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
lastVisited = newSettings.getLast().get(1);
Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle());
Assert.assertFalse(lastVisited.isStarred());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertEquals(1, newSettings.getStarred().size());
starred = newSettings.getStarred().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle());
//TEST renaming in the cache.
savedDashboard1.setTitle(RandomStringUtils.randomAlphanumeric(10));
savedDashboard1 = doPost("/api/dashboard", savedDashboard1, Dashboard.class);
savedDashboard2.setTitle(RandomStringUtils.randomAlphanumeric(10));
savedDashboard2 = doPost("/api/dashboard", savedDashboard2, Dashboard.class);
newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/unstar", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
lastVisited = newSettings.getLast().get(1);
Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle());
Assert.assertFalse(lastVisited.isStarred());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertEquals(1, newSettings.getStarred().size());
starred = newSettings.getStarred().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle());
doDelete("/api/dashboard/" + savedDashboard1.getId().getId().toString()).andExpect(status().isOk());
newSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(1, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
Assert.assertEquals(1, newSettings.getStarred().size());
starred = newSettings.getStarred().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle());
doDelete("/api/dashboard/" + savedDashboard2.getId().getId().toString()).andExpect(status().isOk());
retrievedSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class);
Assert.assertNotNull(retrievedSettings);
Assert.assertNotNull(retrievedSettings.getLast());
Assert.assertTrue(retrievedSettings.getLast().isEmpty());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertTrue(retrievedSettings.getStarred().isEmpty());
}
} }

73
application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java

@ -26,6 +26,8 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@ -35,6 +37,7 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmCountQuery;
import org.thingsboard.server.common.data.query.DeviceTypeFilter; import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityData;
@ -51,6 +54,8 @@ import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import org.thingsboard.server.service.subscription.SubscriptionErrorCode;
import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate;
@ -237,6 +242,74 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
Assert.assertEquals(0, update4.getCount()); Assert.assertEquals(0, update4.getCount());
} }
@Test
public void testAlarmCountWsCmd() throws Exception {
loginTenantAdmin();
AlarmCountCmd cmd1 = new AlarmCountCmd(1, new AlarmCountQuery());
getWsClient().send(cmd1);
AlarmCountUpdate update = getWsClient().parseAlarmCountReply(getWsClient().waitForReply());
Assert.assertEquals(1, update.getCmdId());
Assert.assertEquals(0, update.getCount());
Alarm alarm = new Alarm();
alarm.setOriginator(tenantId);
alarm.setType("TEST ALARM");
alarm.setSeverity(AlarmSeverity.WARNING);
alarm = doPost("/api/alarm", alarm, Alarm.class);
AlarmCountCmd cmd2 = new AlarmCountCmd(2, new AlarmCountQuery());
getWsClient().send(cmd2);
update = getWsClient().parseAlarmCountReply(getWsClient().waitForReply());
Assert.assertEquals(2, update.getCmdId());
Assert.assertEquals(1, update.getCount());
AlarmCountCmd cmd3 = new AlarmCountCmd(3, AlarmCountQuery.builder().assigneeId(tenantAdminUserId).build());
getWsClient().send(cmd3);
update = getWsClient().parseAlarmCountReply(getWsClient().waitForReply());
Assert.assertEquals(3, update.getCmdId());
Assert.assertEquals(0, update.getCount());
alarm.setAssigneeId(tenantAdminUserId);
alarm = doPost("/api/alarm", alarm, Alarm.class);
AlarmCountCmd cmd4 = new AlarmCountCmd(4, AlarmCountQuery.builder().assigneeId(tenantAdminUserId).build());
getWsClient().send(cmd4);
update = getWsClient().parseAlarmCountReply(getWsClient().waitForReply());
Assert.assertEquals(4, update.getCmdId());
Assert.assertEquals(1, update.getCount());
AlarmCountCmd cmd5 = new AlarmCountCmd(5,
AlarmCountQuery.builder().severityList(Collections.singletonList(AlarmSeverity.CRITICAL)).build());
getWsClient().send(cmd5);
update = getWsClient().parseAlarmCountReply(getWsClient().waitForReply());
Assert.assertEquals(5, update.getCmdId());
Assert.assertEquals(0, update.getCount());
alarm.setSeverity(AlarmSeverity.CRITICAL);
doPost("/api/alarm", alarm, Alarm.class);
AlarmCountCmd cmd6 = new AlarmCountCmd(6,
AlarmCountQuery.builder().severityList(Collections.singletonList(AlarmSeverity.CRITICAL)).build());
getWsClient().send(cmd6);
update = getWsClient().parseAlarmCountReply(getWsClient().waitForReply());
Assert.assertEquals(6, update.getCmdId());
Assert.assertEquals(1, update.getCount());
}
@Test @Test
public void testEntityDataLatestWidgetFlow() throws Exception { public void testEntityDataLatestWidgetFlow() throws Exception {
List<EntityKey> keys = List.of(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); List<EntityKey> keys = List.of(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));

12
application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java

@ -30,6 +30,8 @@ import org.thingsboard.server.common.data.query.EntityFilter;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd;
@ -115,6 +117,12 @@ public class TbTestWebSocketClient extends WebSocketClient {
this.send(JacksonUtil.toString(wrapper)); this.send(JacksonUtil.toString(wrapper));
} }
public void send(AlarmCountCmd cmd) throws NotYetConnectedException {
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setAlarmCountCmds(Collections.singletonList(cmd));
this.send(JacksonUtil.toString(wrapper));
}
public String waitForUpdate() { public String waitForUpdate() {
return waitForUpdate(false); return waitForUpdate(false);
} }
@ -179,6 +187,10 @@ public class TbTestWebSocketClient extends WebSocketClient {
return JacksonUtil.fromString(msg, EntityCountUpdate.class); return JacksonUtil.fromString(msg, EntityCountUpdate.class);
} }
public AlarmCountUpdate parseAlarmCountReply(String msg) {
return JacksonUtil.fromString(msg, AlarmCountUpdate.class);
}
public EntityDataUpdate subscribeLatestUpdate(List<EntityKey> keys, EntityFilter entityFilter) { public EntityDataUpdate subscribeLatestUpdate(List<EntityKey> keys, EntityFilter entityFilter) {
EntityDataQuery edq = new EntityDataQuery(entityFilter, new EntityDataPageLink(1, 0, null, null), EntityDataQuery edq = new EntityDataQuery(entityFilter, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); Collections.emptyList(), Collections.emptyList(), Collections.emptyList());

23
application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java

@ -19,12 +19,14 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.google.protobuf.AbstractMessage; import com.google.protobuf.AbstractMessage;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
@ -33,6 +35,7 @@ import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -42,8 +45,11 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest {
public void testSendAlarmToCloud() throws Exception { public void testSendAlarmToCloud() throws Exception {
Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge(); Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge();
UUID alarmUUID = UUID.randomUUID();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
AlarmUpdateMsg.Builder alarmUpdateMgBuilder = AlarmUpdateMsg.newBuilder(); AlarmUpdateMsg.Builder alarmUpdateMgBuilder = AlarmUpdateMsg.newBuilder();
alarmUpdateMgBuilder.setIdMSB(alarmUUID.getMostSignificantBits());
alarmUpdateMgBuilder.setIdLSB(alarmUUID.getLeastSignificantBits());
alarmUpdateMgBuilder.setName("alarm from edge"); alarmUpdateMgBuilder.setName("alarm from edge");
alarmUpdateMgBuilder.setStatus(AlarmStatus.ACTIVE_UNACK.name()); alarmUpdateMgBuilder.setStatus(AlarmStatus.ACTIVE_UNACK.name());
alarmUpdateMgBuilder.setSeverity(AlarmSeverity.CRITICAL.name()); alarmUpdateMgBuilder.setSeverity(AlarmSeverity.CRITICAL.name());
@ -65,6 +71,7 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest {
Optional<AlarmInfo> foundAlarm = alarms.stream().filter(alarm -> alarm.getType().equals("alarm from edge")).findAny(); Optional<AlarmInfo> foundAlarm = alarms.stream().filter(alarm -> alarm.getType().equals("alarm from edge")).findAny();
Assert.assertTrue(foundAlarm.isPresent()); Assert.assertTrue(foundAlarm.isPresent());
AlarmInfo alarmInfo = foundAlarm.get(); AlarmInfo alarmInfo = foundAlarm.get();
Assert.assertEquals(new AlarmId(alarmUUID), alarmInfo.getId());
Assert.assertEquals(device.getId(), alarmInfo.getOriginator()); Assert.assertEquals(device.getId(), alarmInfo.getOriginator());
Assert.assertEquals(AlarmStatus.ACTIVE_UNACK, alarmInfo.getStatus()); Assert.assertEquals(AlarmStatus.ACTIVE_UNACK, alarmInfo.getStatus());
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmInfo.getSeverity()); Assert.assertEquals(AlarmSeverity.CRITICAL, alarmInfo.getSeverity());
@ -85,12 +92,28 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest {
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, alarmUpdateMsg.getMsgType());
Assert.assertEquals(savedAlarm.getUuidId().getMostSignificantBits(), alarmUpdateMsg.getIdMSB());
Assert.assertEquals(savedAlarm.getUuidId().getLeastSignificantBits(), alarmUpdateMsg.getIdLSB());
Assert.assertEquals(savedAlarm.getType(), alarmUpdateMsg.getType()); Assert.assertEquals(savedAlarm.getType(), alarmUpdateMsg.getType());
Assert.assertEquals(savedAlarm.getName(), alarmUpdateMsg.getName()); Assert.assertEquals(savedAlarm.getName(), alarmUpdateMsg.getName());
Assert.assertEquals(device.getName(), alarmUpdateMsg.getOriginatorName()); Assert.assertEquals(device.getName(), alarmUpdateMsg.getOriginatorName());
Assert.assertEquals(savedAlarm.getStatus().name(), alarmUpdateMsg.getStatus()); Assert.assertEquals(savedAlarm.getStatus().name(), alarmUpdateMsg.getStatus());
Assert.assertEquals(savedAlarm.getSeverity().name(), alarmUpdateMsg.getSeverity()); Assert.assertEquals(savedAlarm.getSeverity().name(), alarmUpdateMsg.getSeverity());
// update alarm
String updatedDetails = "{\"testKey\":\"testValue\"}";
savedAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(updatedDetails));
edgeImitator.expectMessageAmount(1);
savedAlarm = doPost("/api/alarm", savedAlarm, Alarm.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, alarmUpdateMsg.getMsgType());
Assert.assertEquals(savedAlarm.getUuidId().getMostSignificantBits(), alarmUpdateMsg.getIdMSB());
Assert.assertEquals(savedAlarm.getUuidId().getLeastSignificantBits(), alarmUpdateMsg.getIdLSB());
Assert.assertEquals(updatedDetails, alarmUpdateMsg.getDetails());
// ack alarm // ack alarm
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doPost("/api/alarm/" + savedAlarm.getUuidId() + "/ack"); doPost("/api/alarm/" + savedAlarm.getUuidId() + "/ack");

54
application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java

@ -15,11 +15,17 @@
*/ */
package org.thingsboard.server.edge; package org.thingsboard.server.edge;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.protobuf.AbstractMessage; import com.google.protobuf.AbstractMessage;
import org.awaitility.Awaitility;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
@ -27,9 +33,11 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg; import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest { abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest {
@ -233,4 +241,50 @@ abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest {
Assert.assertEquals("value1", keyValueProto.getStringV()); Assert.assertEquals("value1", keyValueProto.getStringV());
} }
@Test
public void testSendAttributesDeleteRequestToCloud_nonDeviceEntity() throws Exception {
edgeImitator.expectMessageAmount(2);
Asset savedAsset = saveAsset("Delete Attribute Test");
doPost("/api/edge/" + edge.getUuidId() + "/asset/" + savedAsset.getUuidId(), Asset.class);
Assert.assertTrue(edgeImitator.waitForMessages());
final String attributeKey = "key1";
ObjectNode attributesData = JacksonUtil.OBJECT_MAPPER.createObjectNode();
attributesData.put(attributeKey, "value1");
doPost("/api/plugins/telemetry/ASSET/" + savedAsset.getId() + "/attributes/" + DataConstants.SERVER_SCOPE, attributesData);
// Wait before device attributes saved to database before deleting them
Awaitility.await()
.atMost(10, TimeUnit.SECONDS)
.until(() -> {
String urlTemplate = "/api/plugins/telemetry/ASSET/" + savedAsset.getId() + "/keys/attributes/" + DataConstants.SERVER_SCOPE;
List<String> actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() {});
return actualKeys != null && !actualKeys.isEmpty() && actualKeys.contains(attributeKey);
});
EntityDataProto.Builder builder = EntityDataProto.newBuilder()
.setEntityIdMSB(savedAsset.getUuidId().getMostSignificantBits())
.setEntityIdLSB(savedAsset.getUuidId().getLeastSignificantBits())
.setEntityType(savedAsset.getId().getEntityType().name());
AttributeDeleteMsg.Builder attributeDeleteMsg = AttributeDeleteMsg.newBuilder();
attributeDeleteMsg.setScope(DataConstants.SERVER_SCOPE);
attributeDeleteMsg.addAllAttributeNames(List.of(attributeKey));
attributeDeleteMsg.build();
builder.setAttributeDeleteMsg(attributeDeleteMsg);
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
uplinkMsgBuilder.addEntityData(builder.build());
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
Awaitility.await()
.atMost(10, TimeUnit.SECONDS)
.until(() -> {
String urlTemplate = "/api/plugins/telemetry/ASSET/" + savedAsset.getId() + "/keys/attributes/" + DataConstants.SERVER_SCOPE;
List<String> actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() {});
return actualKeys != null && actualKeys.isEmpty();
});
}
} }

4
application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.entitiy.alarm; package org.thingsboard.server.service.entitiy.alarm;
import com.google.common.util.concurrent.Futures;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -30,8 +29,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;

6
application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java

@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.notification.NotificationRequestConfig
import org.thingsboard.server.common.data.notification.NotificationRequestInfo; import org.thingsboard.server.common.data.notification.NotificationRequestInfo;
import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.UserOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
@ -44,8 +43,8 @@ import org.thingsboard.server.common.data.notification.template.DeliveryMethodNo
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
@ -110,12 +109,9 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
protected NotificationRequest submitNotificationRequest(List<NotificationTargetId> targets, NotificationTemplateId notificationTemplateId, int delayInSec) { protected NotificationRequest submitNotificationRequest(List<NotificationTargetId> targets, NotificationTemplateId notificationTemplateId, int delayInSec) {
NotificationRequestConfig config = new NotificationRequestConfig(); NotificationRequestConfig config = new NotificationRequestConfig();
config.setSendingDelayInSec(delayInSec); config.setSendingDelayInSec(delayInSec);
UserOriginatedNotificationInfo notificationInfo = new UserOriginatedNotificationInfo();
notificationInfo.setDescription("My description");
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.targets(targets.stream().map(UUIDBased::getId).collect(Collectors.toList())) .targets(targets.stream().map(UUIDBased::getId).collect(Collectors.toList()))
.templateId(notificationTemplateId) .templateId(notificationTemplateId)
.info(notificationInfo)
.additionalConfig(config) .additionalConfig(config)
.build(); .build();
return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); return doPost("/api/notification/request", notificationRequest, NotificationRequest.class);

78
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.notification; package org.thingsboard.server.service.notification;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.assertj.core.data.Offset; import org.assertj.core.data.Offset;
import org.java_websocket.client.WebSocketClient; import org.java_websocket.client.WebSocketClient;
@ -25,7 +24,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
@ -35,11 +33,9 @@ import org.thingsboard.server.common.data.notification.NotificationRequestPrevie
import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.UserOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter; import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
@ -49,11 +45,9 @@ import org.thingsboard.server.common.data.notification.template.DeliveryMethodNo
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.notification.NotificationDao; import org.thingsboard.server.dao.notification.NotificationDao;
@ -66,8 +60,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.type; import static org.assertj.core.api.InstanceOfAssertFactories.type;
@ -340,8 +332,8 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
target1Config.setUsersFilter(userListFilter); target1Config.setUsersFilter(userListFilter);
target1.setConfiguration(target1Config); target1.setConfiguration(target1Config);
target1 = saveNotificationTarget(target1); target1 = saveNotificationTarget(target1);
List<UserId> recipients = new ArrayList<>(); List<String> recipients = new ArrayList<>();
recipients.add(tenantAdminUserId); recipients.add(TENANT_ADMIN_EMAIL);
createDifferentCustomer(); createDifferentCustomer();
loginTenantAdmin(); loginTenantAdmin();
@ -353,7 +345,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
customerUser.setCustomerId(differentCustomerId); customerUser.setCustomerId(differentCustomerId);
customerUser.setEmail("other-customer-" + i + "@thingsboard.org"); customerUser.setEmail("other-customer-" + i + "@thingsboard.org");
customerUser = createUser(customerUser, "12345678"); customerUser = createUser(customerUser, "12345678");
recipients.add(customerUser.getId()); recipients.add(customerUser.getEmail());
} }
NotificationTarget target2 = new NotificationTarget(); NotificationTarget target2 = new NotificationTarget();
target2.setName("Other customer users"); target2.setName("Other customer users");
@ -377,7 +369,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
WebDeliveryMethodNotificationTemplate webNotificationTemplate = new WebDeliveryMethodNotificationTemplate(); WebDeliveryMethodNotificationTemplate webNotificationTemplate = new WebDeliveryMethodNotificationTemplate();
webNotificationTemplate.setEnabled(true); webNotificationTemplate.setEnabled(true);
webNotificationTemplate.setBody("Message for WEB: ${recipientEmail}"); webNotificationTemplate.setBody("Message for WEB: ${recipientEmail} ${unknownParam}");
webNotificationTemplate.setSubject("Subject for WEB: ${recipientEmail}"); webNotificationTemplate.setSubject("Subject for WEB: ${recipientEmail}");
templates.put(NotificationDeliveryMethod.WEB, webNotificationTemplate); templates.put(NotificationDeliveryMethod.WEB, webNotificationTemplate);
@ -409,14 +401,14 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(preview.getRecipientsCountByTarget().get(target1.getName())).isEqualTo(1); assertThat(preview.getRecipientsCountByTarget().get(target1.getName())).isEqualTo(1);
assertThat(preview.getRecipientsCountByTarget().get(target2.getName())).isEqualTo(customerUsersCount); assertThat(preview.getRecipientsCountByTarget().get(target2.getName())).isEqualTo(customerUsersCount);
assertThat(preview.getTotalRecipientsCount()).isEqualTo(1 + customerUsersCount); assertThat(preview.getTotalRecipientsCount()).isEqualTo(1 + customerUsersCount);
assertThat(preview.getRecipientsPreview()).extracting(User::getId).containsAll(recipients); assertThat(preview.getRecipientsPreview()).containsAll(recipients);
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = preview.getProcessedTemplates(); Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = preview.getProcessedTemplates();
assertThat(processedTemplates.get(NotificationDeliveryMethod.WEB)).asInstanceOf(type(WebDeliveryMethodNotificationTemplate.class)) assertThat(processedTemplates.get(NotificationDeliveryMethod.WEB)).asInstanceOf(type(WebDeliveryMethodNotificationTemplate.class))
.satisfies(template -> { .satisfies(template -> {
assertThat(template.getBody()) assertThat(template.getBody())
.startsWith("Message for WEB") .startsWith("Message for WEB")
.endsWith(requestorEmail); .endsWith(requestorEmail + " ${unknownParam}");
assertThat(template.getSubject()) assertThat(template.getSubject())
.startsWith("Subject for WEB") .startsWith("Subject for WEB")
.endsWith(requestorEmail); .endsWith(requestorEmail);
@ -439,7 +431,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class)) assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class))
.satisfies(template -> { .satisfies(template -> {
assertThat(template.getBody()) assertThat(template.getBody())
.isEqualTo("Message for SLACK: "); // ${recipientEmail} should be removed .isEqualTo("Message for SLACK: ${recipientEmail}"); // ${recipientEmail} should not be processed
}); });
} }
@ -475,51 +467,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(1); assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(1);
} }
@Test
public void testNotificationsForALotOfUsers() throws Exception {
int usersCount = 5000;
List<User> users = new ArrayList<>();
for (int i = 1; i <= usersCount; i++) {
User user = new User();
user.setTenantId(tenantId);
user.setAuthority(Authority.TENANT_ADMIN);
user.setEmail("test-user-" + i + "@thingsboard.org");
user = doPost("/api/user", user, User.class);
users.add(user);
}
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(tenantId);
notificationTarget.setName("All my users");
PlatformUsersNotificationTargetConfig config = new PlatformUsersNotificationTargetConfig();
AllUsersFilter filter = new AllUsersFilter();
config.setUsersFilter(filter);
notificationTarget.setConfiguration(config);
notificationTarget = saveNotificationTarget(notificationTarget);
NotificationTargetId notificationTargetId = notificationTarget.getId();
ListenableFuture<NotificationRequest> request = executor.submit(() -> {
return submitNotificationRequest(notificationTargetId, "Hello, ${recipientEmail}", 0, NotificationDeliveryMethod.WEB);
});
await().atMost(10, TimeUnit.SECONDS).until(request::isDone);
NotificationRequest notificationRequest = request.get();
await().atMost(5, TimeUnit.SECONDS)
.pollInterval(200, TimeUnit.MILLISECONDS)
.until(() -> {
PageData<Notification> sentNotifications = notificationDao.findByRequestId(tenantId, notificationRequest.getId(), new PageLink(1));
return sentNotifications.getTotalElements() >= usersCount;
});
PageData<Notification> sentNotifications = notificationDao.findByRequestId(tenantId, notificationRequest.getId(), new PageLink(Integer.MAX_VALUE));
assertThat(sentNotifications.getData()).extracting(Notification::getRecipientId)
.containsAll(users.stream().map(User::getId).collect(Collectors.toSet()));
NotificationRequestStats stats = getStats(notificationRequest.getId());
assertThat(stats.getSent().values().stream().mapToInt(AtomicInteger::get).sum()).isGreaterThanOrEqualTo(usersCount);
}
@Test @Test
public void testSlackNotifications() throws Exception { public void testSlackNotifications() throws Exception {
NotificationSettings settings = new NotificationSettings(); NotificationSettings settings = new NotificationSettings();
@ -537,7 +484,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
NotificationTemplateConfig config = new NotificationTemplateConfig(); NotificationTemplateConfig config = new NotificationTemplateConfig();
SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate(); SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate();
slackNotificationTemplate.setEnabled(true); slackNotificationTemplate.setEnabled(true);
slackNotificationTemplate.setBody("To Slack :) ${recipientEmail}"); slackNotificationTemplate.setBody("To Slack :)");
config.setDeliveryMethodsTemplates(Map.of( config.setDeliveryMethodsTemplates(Map.of(
NotificationDeliveryMethod.SLACK, slackNotificationTemplate NotificationDeliveryMethod.SLACK, slackNotificationTemplate
)); ));
@ -550,14 +497,17 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
notificationTarget.setTenantId(tenantId); notificationTarget.setTenantId(tenantId);
notificationTarget.setName(conversationName + " in Slack"); notificationTarget.setName(conversationName + " in Slack");
SlackNotificationTargetConfig targetConfig = new SlackNotificationTargetConfig(); SlackNotificationTargetConfig targetConfig = new SlackNotificationTargetConfig();
targetConfig.setConversation(new SlackConversation(conversationId, conversationName)); targetConfig.setConversation(SlackConversation.builder()
.id(conversationId)
.title(conversationName)
.build());
notificationTarget.setConfiguration(targetConfig); notificationTarget.setConfiguration(targetConfig);
notificationTarget = saveNotificationTarget(notificationTarget); notificationTarget = saveNotificationTarget(notificationTarget);
NotificationRequest successfulNotificationRequest = submitNotificationRequest(List.of(notificationTarget.getId()), notificationTemplate.getId(), 0); NotificationRequest successfulNotificationRequest = submitNotificationRequest(List.of(notificationTarget.getId()), notificationTemplate.getId(), 0);
await().atMost(2, TimeUnit.SECONDS) await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(successfulNotificationRequest.getId()).isSent()); .until(() -> findNotificationRequest(successfulNotificationRequest.getId()).isSent());
verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq("To Slack :) ")); verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq(slackNotificationTemplate.getBody()));
NotificationRequestStats stats = getStats(successfulNotificationRequest.getId()); NotificationRequestStats stats = getStats(successfulNotificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.SLACK)).hasValue(1); assertThat(stats.getSent().get(NotificationDeliveryMethod.SLACK)).hasValue(1);

4
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -123,7 +123,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@Test @Test
public void testNotificationRuleProcessing_entityActionTrigger() throws Exception { public void testNotificationRuleProcessing_entityActionTrigger() throws Exception {
String notificationSubject = "${actionType}: ${entityType} [${entityId}]"; String notificationSubject = "${actionType}: ${entityType} [${entityId}]";
String notificationText = "User: ${originatorUserName}"; String notificationText = "User: ${userEmail}";
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.GENERAL, notificationSubject, notificationText, NotificationDeliveryMethod.WEB); NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.GENERAL, notificationSubject, notificationText, NotificationDeliveryMethod.WEB);
NotificationRule notificationRule = new NotificationRule(); NotificationRule notificationRule = new NotificationRule();
@ -132,7 +132,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
notificationRule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); notificationRule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION);
EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig(); EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig();
triggerConfig.setEntityType(EntityType.DEVICE); triggerConfig.setEntityTypes(Set.of(EntityType.DEVICE));
triggerConfig.setCreated(true); triggerConfig.setCreated(true);
triggerConfig.setUpdated(true); triggerConfig.setUpdated(true);
triggerConfig.setDeleted(true); triggerConfig.setDeleted(true);

3
common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java

@ -33,10 +33,10 @@ import org.thingsboard.server.common.msg.edge.FromEdgeSyncResponse;
import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest; import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueClusterService; import org.thingsboard.server.queue.TbQueueClusterService;
@ -95,4 +95,5 @@ public interface TbClusterService extends TbQueueClusterService {
void pushEdgeSyncResponseToCore(FromEdgeSyncResponse fromEdgeSyncResponse); void pushEdgeSyncResponseToCore(FromEdgeSyncResponse fromEdgeSyncResponse);
void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action); void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action);
} }

14
common/cluster-api/src/main/proto/queue.proto

@ -31,13 +31,12 @@ message ServiceInfo {
} }
message SystemInfoProto { message SystemInfoProto {
double cpuUsage = 1; int64 cpuUsage = 1;
double totalCpuUsage = 2; int64 cpuCount = 2;
int64 memoryUsage = 3; int64 memoryUsage = 3;
int64 totalMemory = 4; int64 totalMemory = 4;
int64 freeMemory = 5; int64 diskUsage = 5;
int64 freeDiscSpace = 6; int64 totalDiscSpace = 6;
int64 totalDiscSpace = 7;
} }
/** /**
@ -974,6 +973,7 @@ message ToCoreMsg {
EdgeNotificationMsgProto edgeNotificationMsg = 5; EdgeNotificationMsgProto edgeNotificationMsg = 5;
DeviceActivityProto deviceActivityMsg = 6; DeviceActivityProto deviceActivityMsg = 6;
NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7; NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7;
NotificationRuleProcessorMsg notificationRuleProcessorMsg = 8;
} }
/* High priority messages with low latency are handled by ThingsBoard Core Service separately */ /* High priority messages with low latency are handled by ThingsBoard Core Service separately */
@ -1059,3 +1059,7 @@ message NotificationSchedulerServiceMsg {
int64 requestIdLSB = 4; int64 requestIdLSB = 4;
int64 ts = 5; int64 ts = 5;
} }
message NotificationRuleProcessorMsg {
bytes trigger = 1;
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java

@ -19,7 +19,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;

6
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -18,19 +18,21 @@ package org.thingsboard.server.dao.alarm;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmCountQuery;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.entity.EntityDaoService; import org.thingsboard.server.dao.entity.EntityDaoService;
@ -112,4 +114,6 @@ public interface AlarmService extends EntityDaoService {
AlarmDataQuery query, Collection<EntityId> orderedEntityIds); AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
void deleteEntityAlarmRelations(TenantId tenantId, EntityId entityId); void deleteEntityAlarmRelations(TenantId tenantId, EntityId entityId);
long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query);
} }

2
common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -36,6 +36,8 @@ public interface DashboardService extends EntityDaoService {
DashboardInfo findDashboardInfoById(TenantId tenantId, DashboardId dashboardId); DashboardInfo findDashboardInfoById(TenantId tenantId, DashboardId dashboardId);
String findDashboardTitleById(TenantId tenantId, DashboardId dashboardId);
ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(TenantId tenantId, DashboardId dashboardId); ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(TenantId tenantId, DashboardId dashboardId);
Dashboard saveDashboard(Dashboard dashboard); Dashboard saveDashboard(Dashboard dashboard);

3
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java

@ -17,11 +17,11 @@ package org.thingsboard.server.dao.device;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceIdInfo;
import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.DeviceIdInfo;
import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.DeviceSearchQuery;
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;
@ -118,5 +118,4 @@ public interface DeviceService extends EntityDaoService {
PageData<Device> findDevicesByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, PageLink pageLink); PageData<Device> findDevicesByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, PageLink pageLink);
long countByTenantId(TenantId tenantId);
} }

26
common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityCountService.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.entity;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
public interface EntityCountService {
long countByTenantIdAndEntityType(TenantId tenantId, EntityType entityType);
void publishCountEntityEvictEvent(TenantId tenantId, EntityType entityType);
}

4
common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java

@ -26,6 +26,10 @@ public interface EntityDaoService {
Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId); Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId);
default long countByTenantId(TenantId tenantId) {
throw new IllegalArgumentException("Not implemented for " + getEntityType());
}
EntityType getEntityType(); EntityType getEntityType();
} }

6
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java

@ -20,8 +20,10 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@ -41,9 +43,9 @@ public interface NotificationTargetService {
PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId targetId, PageLink pageLink); PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId targetId, PageLink pageLink);
int countRecipientsForNotificationTargetConfig(TenantId tenantId, NotificationTargetConfig targetConfig); PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, PlatformUsersNotificationTargetConfig targetConfig, PageLink pageLink);
PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, CustomerId customerId, NotificationTargetConfig targetConfig, PageLink pageLink); PageData<User> findRecipientsForRuleNotificationTargetConfig(TenantId tenantId, PlatformUsersNotificationTargetConfig targetConfig, RuleOriginatedNotificationInfo info, PageLink pageLink);
void deleteNotificationTargetById(TenantId tenantId, NotificationTargetId id); void deleteNotificationTargetById(TenantId tenantId, NotificationTargetId id);

11
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTrigger.java → common/dao-api/src/main/java/org/thingsboard/server/dao/usage/UsageInfoService.java

@ -13,14 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.common.data.notification.rule.trigger; package org.thingsboard.server.dao.usage;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.UsageInfo;
import org.thingsboard.server.common.data.id.TenantId;
public interface NotificationRuleTrigger { public interface UsageInfoService {
NotificationRuleTriggerType getType(); UsageInfo getUsageInfo(TenantId tenantId);
EntityId getOriginatorEntityId();
} }

9
common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java

@ -18,18 +18,19 @@ package org.thingsboard.server.dao.user;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserSettings; import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import java.util.List; import java.util.List;
public interface UserSettingsService { public interface UserSettingsService {
void updateUserSettings(TenantId tenantId, UserId userId, JsonNode settings); void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings);
UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings); UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings);
UserSettings findUserSettings(TenantId tenantId, UserId userId); UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type);
void deleteUserSettings(TenantId tenantId, UserId userId, List<String> jsonPaths); void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List<String> jsonPaths);
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java

@ -24,7 +24,7 @@ public enum ApiFeature {
JS("jsExecutionApiState", "JavaScript functions execution"), JS("jsExecutionApiState", "JavaScript functions execution"),
EMAIL("emailApiState", "Email messages"), EMAIL("emailApiState", "Email messages"),
SMS("smsApiState", "SMS messages"), SMS("smsApiState", "SMS messages"),
ALARM("alarmApiState", "Created alarms"); ALARM("alarmApiState", "Alarms");
@Getter @Getter
private final String apiStateKey; private final String apiStateKey;

21
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java

@ -19,14 +19,14 @@ import lombok.Getter;
public enum ApiUsageRecordKey { public enum ApiUsageRecordKey {
TRANSPORT_MSG_COUNT(ApiFeature.TRANSPORT, "transportMsgCount", "transportMsgLimit"), TRANSPORT_MSG_COUNT(ApiFeature.TRANSPORT, "transportMsgCount", "transportMsgLimit", "message"),
TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit"), TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit", "data point"),
STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit"), STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit", "data point"),
RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"), RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit", "Rule Engine execution"),
JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"), JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit", "JavaScript execution"),
EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"), EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit", "email message"),
SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"), SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit", "SMS message"),
CREATED_ALARMS_COUNT(ApiFeature.ALARM, "createdAlarmsCount", "createdAlarmsLimit"); CREATED_ALARMS_COUNT(ApiFeature.ALARM, "createdAlarmsCount", "createdAlarmsLimit", "alarm");
private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT}; private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT};
private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT}; private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT};
@ -42,11 +42,14 @@ public enum ApiUsageRecordKey {
private final String apiCountKey; private final String apiCountKey;
@Getter @Getter
private final String apiLimitKey; private final String apiLimitKey;
@Getter
private final String unitLabel;
ApiUsageRecordKey(ApiFeature apiFeature, String apiCountKey, String apiLimitKey) { ApiUsageRecordKey(ApiFeature apiFeature, String apiCountKey, String apiLimitKey, String unitLabel) {
this.apiFeature = apiFeature; this.apiFeature = apiFeature;
this.apiCountKey = apiCountKey; this.apiCountKey = apiCountKey;
this.apiLimitKey = apiLimitKey; this.apiLimitKey = apiLimitKey;
this.unitLabel = unitLabel;
} }
public static ApiUsageRecordKey[] getKeys(ApiFeature feature) { public static ApiUsageRecordKey[] getKeys(ApiFeature feature) {

48
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordState.java

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
import lombok.Data;
import java.io.Serializable;
@Data
public class ApiUsageRecordState implements Serializable {
private final ApiFeature apiFeature;
private final ApiUsageRecordKey key;
private final long threshold;
private final long value;
public String getValueAsString() {
return valueAsString(value);
}
public String getThresholdAsString() {
return valueAsString(threshold);
}
private String valueAsString(long value) {
if (value > 1_000_000 && value % 1_000_000 < 10_000) {
return value / 1_000_000 + "M";
} else if (value > 10_000) {
return String.format("%.2fM", ((double) value) / 1_000_000);
} else {
return value + "";
}
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -42,4 +42,6 @@ public class CacheConstants {
public static final String TWO_FA_VERIFICATION_CODES_CACHE = "twoFaVerificationCodes"; public static final String TWO_FA_VERIFICATION_CODES_CACHE = "twoFaVerificationCodes";
public static final String VERSION_CONTROL_TASK_CACHE = "versionControlTask"; public static final String VERSION_CONTROL_TASK_CACHE = "versionControlTask";
public static final String USER_SETTINGS_CACHE = "userSettings"; public static final String USER_SETTINGS_CACHE = "userSettings";
public static final String DASHBOARD_TITLES_CACHE = "dashboardTitles";
public static final String ENTITY_COUNT_CACHE = "entityCount";
} }

18
common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java

@ -17,6 +17,7 @@ package org.thingsboard.server.common.data;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import java.util.Map; import java.util.Map;
@ -26,18 +27,17 @@ public class SystemInfoData {
private String serviceId; private String serviceId;
@ApiModelProperty(position = 2, value = "Service type.") @ApiModelProperty(position = 2, value = "Service type.")
private String serviceType; private String serviceType;
@ApiModelProperty(position = 3, value = "CPU usage.") @ApiModelProperty(position = 3, value = "CPU usage, in percent.")
private Double cpuUsage; private Long cpuUsage;
@ApiModelProperty(position = 4, value = "Total CPU usage.") @ApiModelProperty(position = 4, value = "Total CPU usage.")
private Double totalCpuUsage; private Long cpuCount;
@ApiModelProperty(position = 5, value = "Memory usage in bytes.") @ApiModelProperty(position = 5, value = "Memory usage, in percent.")
private Long memoryUsage; private Long memoryUsage;
@ApiModelProperty(position = 6, value = "Total memory in bytes.") @ApiModelProperty(position = 6, value = "Total memory in bytes.")
private Long totalMemory; private Long totalMemory;
@ApiModelProperty(position = 6, value = "Free memory in bytes.") @ApiModelProperty(position = 7, value = "Disk usage, in percent.")
private Long freeMemory; private Long discUsage;
@ApiModelProperty(position = 7, value = "Free disc space in bytes.") @ApiModelProperty(position = 8, value = "Total disc space in bytes.")
private Long freeDiscSpace;
@ApiModelProperty(position = 7, value = "Total disc space in bytes.")
private Long totalDiscSpace; private Long totalDiscSpace;
} }

22
common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java

@ -19,14 +19,24 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
@ApiModel @ApiModel
@Data @Data
public class UpdateMessage { public class UpdateMessage implements Serializable {
@ApiModelProperty(position = 1, value = "The message about new platform update available.") @ApiModelProperty(position = 1, value = "'True' if new platform update is available.")
private final String message; private final boolean updateAvailable;
@ApiModelProperty(position = 2, value = "'True' if new platform update is available.") @ApiModelProperty(position = 2, value = "Current ThingsBoard version.")
private final boolean isUpdateAvailable;
@ApiModelProperty(position = 3, value = "Current ThingsBoard version.")
private final String currentVersion; private final String currentVersion;
@ApiModelProperty(position = 3, value = "Latest ThingsBoard version.")
private final String latestVersion;
@ApiModelProperty(position = 4, value = "Upgrade instructions URL.")
private final String upgradeInstructionsUrl;
@ApiModelProperty(position = 5, value = "Current ThingsBoard version release notes URL.")
private final String currentVersionReleaseNotesUrl;
@ApiModelProperty(position = 6, value = "Latest ThingsBoard version release notes URL.")
private final String latestVersionReleaseNotesUrl;
} }

43
common/data/src/main/java/org/thingsboard/server/common/data/UsageInfo.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
import lombok.Data;
@Data
public class UsageInfo {
private long devices;
private long maxDevices;
private long assets;
private long maxAssets;
private long customers;
private long maxCustomers;
private long users;
private long maxUsers;
private long dashboards;
private long maxDashboards;
private long transportMessages;
private long maxTransportMessages;
private long jsExecutions;
private long maxJsExecutions;
private long emails;
private long maxEmails;
private long sms;
private long maxSms;
private long alarms;
private long maxAlarms;
}

20
common/data/src/main/java/org/thingsboard/server/common/data/User.java

@ -30,6 +30,8 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@ApiModel @ApiModel
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId, NotificationRecipient { public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId, NotificationRecipient {
@ -165,6 +167,24 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
return getEmail(); return getEmail();
} }
@JsonIgnore
public String getTitle() {
String title = "";
if (isNotEmpty(firstName)) {
title += firstName;
}
if (isNotEmpty(lastName)) {
if (!title.isEmpty()) {
title += " ";
}
title += lastName;
}
if (title.isEmpty()) {
title = email;
}
return title;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();

8
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java → common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmApiCallResult.java

@ -13,20 +13,18 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.alarm; package org.thingsboard.server.common.data.alarm;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import java.io.Serializable;
import java.util.List; import java.util.List;
@Data @Data
public class AlarmApiCallResult { public class AlarmApiCallResult implements Serializable {
private final boolean successful; private final boolean successful;
private final boolean created; private final boolean created;

8
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java

@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -61,11 +62,17 @@ public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationReques
private UserId userId; private UserId userId;
private AlarmId edgeAlarmId;
public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a) { public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a) {
return fromAlarm(a, null); return fromAlarm(a, null);
} }
public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a, UserId userId) { public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a, UserId userId) {
return fromAlarm(a, userId, null);
}
public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a, UserId userId, AlarmId edgeAlarmId) {
return AlarmCreateOrUpdateActiveRequest.builder() return AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(a.getTenantId()) .tenantId(a.getTenantId())
.customerId(a.getCustomerId()) .customerId(a.getCustomerId())
@ -81,6 +88,7 @@ public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationReques
.propagateToTenant(a.isPropagateToTenant()) .propagateToTenant(a.isPropagateToTenant())
.propagateRelationTypes(a.getPropagateRelationTypes()).build()) .propagateRelationTypes(a.getPropagateRelationTypes()).build())
.userId(userId) .userId(userId)
.edgeAlarmId(edgeAlarmId)
.build(); .build();
} }

3
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestPreview.java

@ -16,7 +16,6 @@
package org.thingsboard.server.common.data.notification; package org.thingsboard.server.common.data.notification;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import java.util.Collection; import java.util.Collection;
@ -28,6 +27,6 @@ public class NotificationRequestPreview {
private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates; private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates;
private int totalRecipientsCount; private int totalRecipientsCount;
private Map<String, Integer> recipientsCountByTarget; private Map<String, Integer> recipientsCountByTarget;
private Collection<User> recipientsPreview; private Collection<String> recipientsPreview;
} }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save