Browse Source

Merge with develop/3.5

pull/7911/head
ViacheslavKlimov 4 years ago
parent
commit
a56261ac9d
  1. 4
      application/src/main/data/json/demo/dashboards/gateways.json
  2. 10
      application/src/main/data/json/demo/dashboards/thermostats.json
  3. 2
      application/src/main/data/json/system/widget_bundles/alarm_widgets.json
  4. 317
      application/src/main/data/upgrade/3.4.4/schema_update.sql
  5. 101
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  6. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  7. 2
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  8. 35
      application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
  9. 6
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  10. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
  11. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
  12. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  13. 5
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java
  14. 165
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  15. 15
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
  16. 2
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  17. 7
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  18. 3
      application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java
  19. 45
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  20. 6
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  21. 27
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  22. 10
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  23. 131
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  24. 1
      application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
  25. 14
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java
  26. 4
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  27. 2
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
  28. 204
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
  29. 1
      application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java
  30. 30
      application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
  31. 85
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java
  32. 21
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
  33. 60
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  34. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
  35. 2
      common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java
  36. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
  37. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  38. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  39. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  40. 29
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  41. 22
      common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java
  42. 22
      common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java
  43. 22
      common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java
  44. 2
      common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java
  45. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
  46. 55
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  47. 37
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java
  48. 30
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssigneeUpdate.java
  49. 87
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java
  50. 56
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java
  51. 34
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.java
  52. 46
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java
  53. 3
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
  54. 24
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSearchStatus.java
  55. 118
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatusFilter.java
  56. 79
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java
  57. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java
  58. 3
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  59. 2
      common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
  60. 3
      common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java
  61. 2
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java
  62. 8
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  63. 27
      common/data/src/main/java/org/thingsboard/server/common/data/id/NameLabelAndCustomerDetails.java
  64. 18
      common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java
  65. 11
      common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataPageLink.java
  66. 25
      common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java
  67. 3
      common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java
  68. 3
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java
  69. 25
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  70. 261
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  71. 2
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  72. 82
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  73. 17
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  74. 45
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java
  75. 49
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java
  76. 2
      dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java
  77. 8
      dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java
  78. 105
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  79. 244
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  80. 28
      dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java
  81. 125
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
  82. 30
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  83. 10
      dao/src/main/resources/sql/schema-entities-idx.sql
  84. 259
      dao/src/main/resources/sql/schema-entities.sql
  85. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java
  86. 320
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java
  87. 1
      dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java
  88. 244
      dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java
  89. 8
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
  90. 49
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java
  91. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmResult.java
  92. 39
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java
  93. 49
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
  94. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
  95. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java
  96. 49
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
  97. 305
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
  98. 17
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java
  99. 122
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java
  100. 46
      ui-ngx/src/app/core/http/alarm-comment.service.ts

4
application/src/main/data/json/demo/dashboards/gateways.json

@ -131,7 +131,7 @@
"name": "Add device",
"icon": "add",
"type": "customPretty",
"customHtml": "<form #addDeviceForm=\"ngForm\" [formGroup]=\"addDeviceFormGroup\"\n (ngSubmit)=\"save()\" style=\"width: 480px;\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Add device</h2>\n <span fxFlex></span>\n <button mat-button mat-icon-button\n (click)=\"cancel()\"\n type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content>\n <div class=\"mat-padding\" fxLayout=\"column\">\n <mat-form-field class=\"mat-block\">\n <mat-label>Device name</mat-label>\n <input matInput formControlName=\"deviceName\" required>\n <mat-error *ngIf=\"addDeviceFormGroup.get('deviceName').hasError('required')\">\n Device name is required.\n </mat-error>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxFlex fxLayout=\"row\" fxLayoutGap=\"8px\">\n <mat-form-field fxFlex=\"50\" class=\"mat-block\">\n <mat-label>Latitude</mat-label>\n <input type=\"number\" step=\"any\" matInput formControlName=\"latitude\">\n </mat-form-field>\n <mat-form-field fxFlex=\"50\" class=\"mat-block\">\n <mat-label>Longitude</mat-label>\n <input type=\"number\" step=\"any\" matInput formControlName=\"longitude\">\n </mat-form-field>\n </div>\n <mat-form-field class=\"mat-block\">\n <mat-label>Label</mat-label>\n <input matInput formControlName=\"deviceLabel\">\n </mat-form-field>\n </div> \n </div>\n <div mat-dialog-actions fxLayout=\"row\">\n <span fxFlex></span>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || addDeviceForm.invalid || !addDeviceForm.dirty\">\n Create\n </button>\n <button mat-button color=\"primary\"\n style=\"margin-right: 20px;\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>\n",
"customHtml": "<form #addDeviceForm=\"ngForm\" [formGroup]=\"addDeviceFormGroup\"\n (ngSubmit)=\"save()\" style=\"width: 480px;\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Add device</h2>\n <span fxFlex></span>\n <button mat-icon-button\n (click)=\"cancel()\"\n type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content>\n <div class=\"mat-padding\" fxLayout=\"column\">\n <mat-form-field class=\"mat-block\">\n <mat-label>Device name</mat-label>\n <input matInput formControlName=\"deviceName\" required>\n <mat-error *ngIf=\"addDeviceFormGroup.get('deviceName').hasError('required')\">\n Device name is required.\n </mat-error>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxFlex fxLayout=\"row\" fxLayoutGap=\"8px\">\n <mat-form-field fxFlex=\"50\" class=\"mat-block\">\n <mat-label>Latitude</mat-label>\n <input type=\"number\" step=\"any\" matInput formControlName=\"latitude\">\n </mat-form-field>\n <mat-form-field fxFlex=\"50\" class=\"mat-block\">\n <mat-label>Longitude</mat-label>\n <input type=\"number\" step=\"any\" matInput formControlName=\"longitude\">\n </mat-form-field>\n </div>\n <mat-form-field class=\"mat-block\">\n <mat-label>Label</mat-label>\n <input matInput formControlName=\"deviceLabel\">\n </mat-form-field>\n </div> \n </div>\n <div mat-dialog-actions fxLayout=\"row\">\n <span fxFlex></span>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || addDeviceForm.invalid || !addDeviceForm.dirty\">\n Create\n </button>\n <button mat-button color=\"primary\"\n style=\"margin-right: 20px;\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>\n",
"customCss": "",
"customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenAddDeviceDialog();\n\nfunction openAddDeviceDialog() {\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\n}\n\nfunction AddDeviceDialogController(instance) {\n let vm = instance;\n \n vm.addDeviceFormGroup = vm.fb.group({\n deviceName: ['', [vm.validators.required]],\n deviceLabel: [''],\n attributes: vm.fb.group({\n latitude: [null],\n longitude: [null]\n }) \n });\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n vm.save = function() {\n vm.addDeviceFormGroup.markAsPristine();\n let device = {\n additionalInfo: {gateway: true},\n name: vm.addDeviceFormGroup.get('deviceName').value,\n type: 'gateway',\n label: vm.addDeviceFormGroup.get('deviceLabel').value\n };\n deviceService.saveDevice(device).subscribe(\n function (device) {\n saveAttributes(device.id).subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n function saveAttributes(entityId) {\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}\n",
"customResources": [],
@ -160,7 +160,7 @@
"name": "Edit device",
"icon": "edit",
"type": "customPretty",
"customHtml": "<form #editDeviceForm=\"ngForm\" [formGroup]=\"editDeviceFormGroup\"\n (ngSubmit)=\"save()\" style=\"width: 480px;\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit device</h2>\n <span fxFlex></span>\n <button mat-button mat-icon-button\n (click)=\"cancel()\"\n type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content>\n <div class=\"mat-padding\" fxLayout=\"column\">\n <mat-form-field class=\"mat-block\">\n <mat-label>Device name</mat-label>\n <input matInput formControlName=\"deviceName\" required>\n <mat-error *ngIf=\"editDeviceFormGroup.get('deviceName').hasError('required')\">\n Device name is required.\n </mat-error>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxFlex fxLayout=\"row\" fxLayoutGap=\"8px\">\n <mat-form-field fxFlex=\"50\" class=\"mat-block\">\n <mat-label>Latitude</mat-label>\n <input type=\"number\" step=\"any\" matInput formControlName=\"latitude\">\n </mat-form-field>\n <mat-form-field fxFlex=\"50\" class=\"mat-block\">\n <mat-label>Longitude</mat-label>\n <input type=\"number\" step=\"any\" matInput formControlName=\"longitude\">\n </mat-form-field>\n </div>\n <mat-form-field class=\"mat-block\">\n <mat-label>Label</mat-label>\n <input matInput formControlName=\"deviceLabel\">\n </mat-form-field>\n </div> \n </div>\n <div mat-dialog-actions fxLayout=\"row\">\n <span fxFlex></span>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editDeviceForm.invalid || !editDeviceForm.dirty\">\n Update\n </button>\n <button mat-button color=\"primary\"\n style=\"margin-right: 20px;\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>\n",
"customHtml": "<form #editDeviceForm=\"ngForm\" [formGroup]=\"editDeviceFormGroup\"\n (ngSubmit)=\"save()\" style=\"width: 480px;\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Edit device</h2>\n <span fxFlex></span>\n <button mat-icon-button\n (click)=\"cancel()\"\n type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content>\n <div class=\"mat-padding\" fxLayout=\"column\">\n <mat-form-field class=\"mat-block\">\n <mat-label>Device name</mat-label>\n <input matInput formControlName=\"deviceName\" required>\n <mat-error *ngIf=\"editDeviceFormGroup.get('deviceName').hasError('required')\">\n Device name is required.\n </mat-error>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxFlex fxLayout=\"row\" fxLayoutGap=\"8px\">\n <mat-form-field fxFlex=\"50\" class=\"mat-block\">\n <mat-label>Latitude</mat-label>\n <input type=\"number\" step=\"any\" matInput formControlName=\"latitude\">\n </mat-form-field>\n <mat-form-field fxFlex=\"50\" class=\"mat-block\">\n <mat-label>Longitude</mat-label>\n <input type=\"number\" step=\"any\" matInput formControlName=\"longitude\">\n </mat-form-field>\n </div>\n <mat-form-field class=\"mat-block\">\n <mat-label>Label</mat-label>\n <input matInput formControlName=\"deviceLabel\">\n </mat-form-field>\n </div> \n </div>\n <div mat-dialog-actions fxLayout=\"row\">\n <span fxFlex></span>\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editDeviceForm.invalid || !editDeviceForm.dirty\">\n Update\n </button>\n <button mat-button color=\"primary\"\n style=\"margin-right: 20px;\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>\n",
"customCss": "/*=======================================================================*/\n/*========== There are two examples: for edit and add entity ==========*/\n/*=======================================================================*/\n/*======================== Edit entity example ========================*/\n/*=======================================================================*/\n/*\n.edit-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.edit-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.edit-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n.relations-list .header {\n padding-right: 5px;\n padding-bottom: 5px;\n padding-left: 5px;\n}\n\n.relations-list .header .cell {\n padding-right: 5px;\n padding-left: 5px;\n font-size: 12px;\n font-weight: 700;\n color: rgba(0, 0, 0, .54);\n white-space: nowrap;\n}\n\n.relations-list .body {\n padding-right: 5px;\n padding-bottom: 15px;\n padding-left: 5px;\n}\n\n.relations-list .body .row {\n padding-top: 5px;\n}\n\n.relations-list .body .cell {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.relations-list .body md-autocomplete-wrap md-input-container {\n height: 30px;\n}\n\n.relations-list .body .md-button {\n margin: 0;\n}\n\n.relations-list.old-relations tb-entity-select tb-entity-autocomplete button {\n display: none;\n} \n*/\n/*========================================================================*/\n/*========================= Add entity example =========================*/\n/*========================================================================*/\n/*\n.add-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.add-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.add-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n.relations-list .header {\n padding-right: 5px;\n padding-bottom: 5px;\n padding-left: 5px;\n}\n\n.relations-list .header .cell {\n padding-right: 5px;\n padding-left: 5px;\n font-size: 12px;\n font-weight: 700;\n color: rgba(0, 0, 0, .54);\n white-space: nowrap;\n}\n\n.relations-list .body {\n padding-right: 5px;\n padding-bottom: 15px;\n padding-left: 5px;\n}\n\n.relations-list .body .row {\n padding-top: 5px;\n}\n\n.relations-list .body .cell {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.relations-list .body md-autocomplete-wrap md-input-container {\n height: 30px;\n}\n\n.relations-list .body .md-button {\n margin: 0;\n}\n*/\n",
"customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenEditDeviceDialog();\n\nfunction openEditDeviceDialog() {\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\n}\n\nfunction EditDeviceDialogController(instance) {\n let vm = instance;\n \n vm.device = null;\n vm.attributes = {};\n \n vm.editDeviceFormGroup = vm.fb.group({\n deviceName: ['', [vm.validators.required]],\n deviceLabel: [''],\n attributes: vm.fb.group({\n latitude: [null],\n longitude: [null]\n }) \n });\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n vm.save = function() {\n vm.editDeviceFormGroup.markAsPristine();\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value;\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value;\n deviceService.saveDevice(vm.device).subscribe(\n function () {\n saveAttributes().subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n getEntityInfo();\n \n function getEntityInfo() {\n deviceService.getDevice(entityId.id).subscribe(\n function (device) {\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\n ['latitude', 'longitude']).subscribe(\n function (attributes) {\n for (let i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value; \n }\n vm.device = device;\n vm.editDeviceFormGroup.patchValue(\n {\n deviceName: vm.device.name,\n deviceLabel: vm.device.label,\n attributes: {\n latitude: vm.attributes.latitude,\n longitude: vm.attributes.longitude\n }\n }, {emitEvent: false}\n );\n } \n );\n }\n ); \n }\n \n function saveAttributes() {\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}\n",
"customResources": [],

10
application/src/main/data/json/demo/dashboards/thermostats.json

@ -215,6 +215,8 @@
"displayDetails": true,
"allowAcknowledgment": true,
"allowClear": true,
"allowAssign": true,
"displayComments": true,
"displayPagination": true,
"defaultPageSize": 10,
"defaultSortOrder": "-createdTime",
@ -277,6 +279,14 @@
"color": "#607d8b",
"settings": {},
"_hash": 0.7977920750136249
},
{
"name": "assignee",
"type": "alarm",
"label": "Assignee",
"color": "#9c27b0",
"settings": {},
"_hash": 0.8678751039018493
}
]
},

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

@ -23,7 +23,7 @@
"dataKeySettingsSchema": "",
"settingsDirective": "tb-alarms-table-widget-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,\"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}],\"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,\"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}"
}
}
]

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

@ -14,6 +14,70 @@
-- limitations under the License.
--
-- USER CREDENTIALS START
ALTER TABLE user_credentials
ADD COLUMN IF NOT EXISTS additional_info varchar NOT NULL DEFAULT '{}';
UPDATE user_credentials
SET additional_info = json_build_object('userPasswordHistory', (u.additional_info::json -> 'userPasswordHistory'))
FROM tb_user u WHERE user_credentials.user_id = u.id AND u.additional_info::jsonb ? 'userPasswordHistory';
UPDATE tb_user SET additional_info = tb_user.additional_info::jsonb - 'userPasswordHistory' WHERE additional_info::jsonb ? 'userPasswordHistory';
-- USER CREDENTIALS END
-- ALARM ASSIGN TO USER START
ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assign_ts BIGINT;
ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assignee_id UUID;
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_assignee_created_time ON alarm(tenant_id, assignee_id, created_time DESC);
-- ALARM ASSIGN TO USER END
-- ALARM STATUS REFACTORING START
ALTER TABLE alarm ADD COLUMN IF NOT EXISTS acknowledged boolean;
ALTER TABLE alarm ADD COLUMN IF NOT EXISTS cleared boolean;
ALTER TABLE alarm ADD COLUMN IF NOT EXISTS status varchar; -- to avoid failure of the subsequent upgrade.
UPDATE alarm SET acknowledged = true, cleared = true WHERE status = 'CLEARED_ACK';
UPDATE alarm SET acknowledged = true, cleared = false WHERE status = 'ACTIVE_ACK';
UPDATE alarm SET acknowledged = false, cleared = true WHERE status = 'CLEARED_UNACK';
UPDATE alarm SET acknowledged = false, cleared = false WHERE status = 'ACTIVE_UNACK';
-- Drop index by 'status' column and replace with new one that has only active alarms;
DROP INDEX IF EXISTS idx_alarm_originator_alarm_type_active;
CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type_active
ON alarm USING btree (originator_id, type) WHERE cleared = false;
-- Cover index by alarm type to optimize propagated alarm queries;
DROP INDEX IF EXISTS idx_entity_alarm_entity_id_alarm_type_created_time_alarm_id;
CREATE INDEX IF NOT EXISTS idx_entity_alarm_entity_id_alarm_type_created_time_alarm_id ON entity_alarm
USING btree (tenant_id, entity_id, alarm_type, created_time DESC) INCLUDE(alarm_id);
DROP INDEX IF EXISTS idx_alarm_tenant_status_created_time;
ALTER TABLE alarm DROP COLUMN IF EXISTS status;
-- Update old alarms and set their state to clear, if there are newer alarms.
UPDATE alarm a
SET cleared = TRUE
WHERE cleared = FALSE
AND id != (SELECT l.id
FROM alarm l
WHERE l.tenant_id = a.tenant_id
AND l.originator_id = a.originator_id
AND l.type = a.type
ORDER BY l.created_time DESC, l.id
LIMIT 1);
VACUUM FULL ANALYZE alarm;
-- ALARM STATUS REFACTORING END
-- ALARM COMMENTS START
CREATE TABLE IF NOT EXISTS alarm_comment (
id uuid NOT NULL,
created_time bigint NOT NULL,
@ -101,3 +165,256 @@ CREATE TABLE IF NOT EXISTS user_settings (
settings varchar(100000),
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE
);
-- ALARM COMMENTS END
-- ALARM INFO VIEW
DROP VIEW IF EXISTS alarm_info CASCADE;
CREATE VIEW alarm_info AS
SELECT a.*,
(CASE WHEN a.acknowledged AND a.cleared THEN 'CLEARED_ACK'
WHEN NOT a.acknowledged AND a.cleared THEN 'CLEARED_UNACK'
WHEN a.acknowledged AND NOT a.cleared THEN 'ACTIVE_ACK'
WHEN NOT a.acknowledged AND NOT a.cleared THEN 'ACTIVE_UNACK' END) as status,
COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id)
WHEN a.originator_type = 1 THEN (select title 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 = 3 THEN (select title from dashboard where id = a.originator_id)
WHEN a.originator_type = 4 THEN (select name from asset where id = a.originator_id)
WHEN a.originator_type = 5 THEN (select 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 = 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 = 18 THEN (select name from edge where id = a.originator_id) END
, 'Deleted') originator_name,
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 = 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 = 4 THEN (select COALESCE(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 = 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 = 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
, 'Deleted') as originator_label,
u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email
FROM alarm a
LEFT JOIN tb_user u ON u.id = a.assignee_id;
-- ALARM INFO VIEW END
-- ALARM FUNCTIONS START
DROP FUNCTION IF EXISTS create_or_update_active_alarm;
CREATE OR REPLACE FUNCTION create_or_update_active_alarm(
t_id uuid, c_id uuid, a_id uuid, a_created_ts bigint,
a_o_id uuid, a_o_type integer, a_type varchar,
a_severity varchar, a_start_ts bigint, a_end_ts bigint,
a_details varchar,
a_propagate boolean, a_propagate_to_owner boolean,
a_propagate_to_tenant boolean, a_propagation_types varchar,
a_creation_enabled boolean)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
null_id constant uuid = '13814000-1dd2-11b2-8080-808080808080'::uuid;
existing alarm;
result alarm_info;
row_count integer;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.originator_id = a_o_id AND a.type = a_type AND a.cleared = false ORDER BY a.start_ts DESC FOR UPDATE;
IF existing.id IS NULL THEN
IF a_creation_enabled = FALSE THEN
RETURN json_build_object('success', false)::text;
END IF;
IF c_id = null_id THEN
c_id = NULL;
end if;
INSERT INTO alarm
(tenant_id, customer_id, id, created_time,
originator_id, originator_type, type,
severity, start_ts, end_ts,
additional_info,
propagate, propagate_to_owner, propagate_to_tenant, propagate_relation_types,
acknowledged, ack_ts,
cleared, clear_ts,
assignee_id, assign_ts)
VALUES
(t_id, c_id, a_id, a_created_ts,
a_o_id, a_o_type, a_type,
a_severity, a_start_ts, a_end_ts,
a_details,
a_propagate, a_propagate_to_owner, a_propagate_to_tenant, a_propagation_types,
false, 0, false, 0, NULL, 0);
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'created', true, 'modified', true, 'alarm', row_to_json(result))::text;
ELSE
UPDATE alarm a
SET severity = a_severity,
start_ts = a_start_ts,
end_ts = a_end_ts,
additional_info = a_details,
propagate = a_propagate,
propagate_to_owner = a_propagate_to_owner,
propagate_to_tenant = a_propagate_to_tenant,
propagate_relation_types = a_propagation_types
WHERE a.id = existing.id
AND a.tenant_id = t_id
AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
GET DIAGNOSTICS row_count = ROW_COUNT;
SELECT * INTO result FROM alarm_info a WHERE a.id = existing.id AND a.tenant_id = t_id;
IF row_count > 0 THEN
RETURN json_build_object('success', true, 'modified', true, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
ELSE
RETURN json_build_object('success', true, 'modified', false, 'alarm', row_to_json(result))::text;
END IF;
END IF;
END
$$;
DROP FUNCTION IF EXISTS update_alarm;
CREATE OR REPLACE FUNCTION update_alarm(t_id uuid, a_id uuid, a_severity varchar, a_start_ts bigint, a_end_ts bigint,
a_details varchar,
a_propagate boolean, a_propagate_to_owner boolean,
a_propagate_to_tenant boolean, a_propagation_types varchar)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
row_count integer;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
UPDATE alarm a
SET severity = a_severity,
start_ts = a_start_ts,
end_ts = a_end_ts,
additional_info = a_details,
propagate = a_propagate,
propagate_to_owner = a_propagate_to_owner,
propagate_to_tenant = a_propagate_to_tenant,
propagate_relation_types = a_propagation_types
WHERE a.id = a_id
AND a.tenant_id = t_id
AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
GET DIAGNOSTICS row_count = ROW_COUNT;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
IF row_count > 0 THEN
RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
ELSE
RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result))::text;
END IF;
END
$$;
DROP FUNCTION IF EXISTS acknowledge_alarm;
CREATE OR REPLACE FUNCTION acknowledge_alarm(t_id uuid, a_id uuid, a_ts bigint)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
modified boolean = FALSE;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF NOT (existing.acknowledged) THEN
modified = TRUE;
UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
END
$$;
DROP FUNCTION IF EXISTS clear_alarm;
CREATE OR REPLACE FUNCTION clear_alarm(t_id uuid, a_id uuid, a_ts bigint, a_details varchar)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
cleared boolean = FALSE;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF NOT(existing.cleared) THEN
cleared = TRUE;
UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text;
END
$$;
DROP FUNCTION IF EXISTS assign_alarm;
CREATE OR REPLACE FUNCTION assign_alarm(t_id uuid, a_id uuid, u_id uuid, a_ts bigint)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
modified boolean = FALSE;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF existing.assignee_id IS NULL OR existing.assignee_id != u_id THEN
modified = TRUE;
UPDATE alarm a SET assignee_id = u_id, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
END
$$;
DROP FUNCTION IF EXISTS unassign_alarm;
CREATE OR REPLACE FUNCTION unassign_alarm(t_id uuid, a_id uuid, a_ts bigint)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
modified boolean = FALSE;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF existing.assignee_id IS NOT NULL THEN
modified = TRUE;
UPDATE alarm a SET assignee_id = NULL, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
END
$$;
-- ALARM FUNCTIONS END

101
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -30,6 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
@ -41,16 +42,23 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ALARM_INFO_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ALARM_SORT_PROPERTY_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.ASSIGNEE_ID;
import static org.thingsboard.server.controller.ControllerConstants.ASSIGN_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE;
@ -79,6 +87,7 @@ public class AlarmController extends BaseController {
private static final String ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES = "ANY, ACTIVE, CLEARED, ACK, UNACK";
private static final String ALARM_QUERY_STATUS_DESCRIPTION = "A string value representing one of the AlarmStatus enumeration value";
private static final String ALARM_QUERY_STATUS_ALLOWABLE_VALUES = "ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK";
private static final String ALARM_QUERY_ASSIGNEE_DESCRIPTION = "A string value representing the assignee user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
private static final String ALARM_QUERY_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on of next alarm fields: type, severity or status";
private static final String ALARM_QUERY_START_TIME_DESCRIPTION = "The start timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'.";
private static final String ALARM_QUERY_END_TIME_DESCRIPTION = "The end timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'.";
@ -93,12 +102,8 @@ public class AlarmController extends BaseController {
public Alarm getAlarmById(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION)
@PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException {
checkParameter(ALARM_ID, strAlarmId);
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
return checkAlarmId(alarmId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
return checkAlarmId(alarmId, Operation.READ);
}
@ApiOperation(value = "Get Alarm Info (getAlarmInfoById)",
@ -110,15 +115,11 @@ public class AlarmController extends BaseController {
public AlarmInfo getAlarmInfoById(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION)
@PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException {
checkParameter(ALARM_ID, strAlarmId);
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
return checkAlarmInfoId(alarmId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
return checkAlarmInfoId(alarmId, Operation.READ);
}
@ApiOperation(value = "Create or update Alarm (saveAlarm)",
@ApiOperation(value = "Create or Update Alarm (saveAlarm)",
notes = "Creates or Updates the Alarm. " +
"When creating alarm, platform generates Alarm Id as " + UUID_WIKI_LINK +
"The newly created Alarm id will be present in the response. Specify existing Alarm id to update the alarm. " +
@ -135,7 +136,9 @@ public class AlarmController extends BaseController {
@ResponseBody
public Alarm saveAlarm(@ApiParam(value = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException {
alarm.setTenantId(getTenantId());
checkNotNull(alarm.getOriginator());
checkEntity(alarm.getId(), alarm, Resource.ALARM);
checkEntityId(alarm.getOriginator(), Operation.READ);
return tbAlarmService.save(alarm, getCurrentUser());
}
@ -158,11 +161,12 @@ public class AlarmController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void ackAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception {
public AlarmInfo ackAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception {
checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
tbAlarmService.ack(alarm, getCurrentUser()).get();
//TODO: return correct error code if the alarm is not found or already cleared
return tbAlarmService.ack(alarm, getCurrentUser());
}
@ApiOperation(value = "Clear Alarm (clearAlarm)",
@ -172,11 +176,50 @@ public class AlarmController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void clearAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception {
public AlarmInfo clearAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception {
checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
//TODO: return correct error code if the alarm is not found or already cleared
return tbAlarmService.clear(alarm, getCurrentUser());
}
@ApiOperation(value = "Assign/Reassign Alarm (assignAlarm)",
notes = "Assign the Alarm. " +
"Once assigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_ASSIGNED' " +
"(or ALARM_REASSIGNED in case of assigning already assigned alarm) will be generated. " +
"Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/assign/{assigneeId}", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public Alarm assignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION)
@PathVariable(ALARM_ID) String strAlarmId,
@ApiParam(value = ASSIGN_ID_PARAM_DESCRIPTION)
@PathVariable(ASSIGNEE_ID) String strAssigneeId
) throws Exception {
checkParameter(ALARM_ID, strAlarmId);
checkParameter(ASSIGNEE_ID, strAssigneeId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
tbAlarmService.clear(alarm, getCurrentUser()).get();
UserId assigneeId = new UserId(UUID.fromString(strAssigneeId));
checkUserId(assigneeId, Operation.READ);
return tbAlarmService.assign(alarm, assigneeId, System.currentTimeMillis(), getCurrentUser());
}
@ApiOperation(value = "Unassign Alarm (unassignAlarm)",
notes = "Unassign the Alarm. " +
"Once unassigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_UNASSIGNED' will be generated. " +
"Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/assign", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public Alarm unassignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION)
@PathVariable(ALARM_ID) String strAlarmId
) throws Exception {
checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
return tbAlarmService.unassign(alarm, System.currentTimeMillis(), getCurrentUser());
}
@ApiOperation(value = "Get Alarms (getAlarms)",
@ -194,6 +237,8 @@ public class AlarmController extends BaseController {
@RequestParam(required = false) String searchStatus,
@ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)
@RequestParam(required = false) String status,
@ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION)
@RequestParam(required = false) String assigneeId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@ -221,10 +266,14 @@ public class AlarmController extends BaseController {
"and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
checkEntityId(entityId, Operation.READ);
UserId assigneeUserId = null;
if (assigneeId != null) {
assigneeUserId = new UserId(UUID.fromString(assigneeId));
}
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
try {
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get());
} catch (Exception e) {
throw handleException(e);
}
@ -244,6 +293,8 @@ public class AlarmController extends BaseController {
@RequestParam(required = false) String searchStatus,
@ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)
@RequestParam(required = false) String status,
@ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION)
@RequestParam(required = false) String assigneeId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@ -267,13 +318,17 @@ public class AlarmController extends BaseController {
throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " +
"and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
UserId assigneeUserId = null;
if (assigneeId != null) {
assigneeUserId = new UserId(UUID.fromString(assigneeId));
}
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
try {
if (getCurrentUser().isCustomerUser()) {
return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get());
} else {
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get());
}
} catch (Exception e) {
throw handleException(e);
@ -295,7 +350,9 @@ public class AlarmController extends BaseController {
@ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)
@RequestParam(required = false) String searchStatus,
@ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)
@RequestParam(required = false) String status
@RequestParam(required = false) String status,
@ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION)
@RequestParam(required = false) String assigneeId
) throws ThingsboardException {
checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType);
@ -308,7 +365,7 @@ public class AlarmController extends BaseController {
}
checkEntityId(entityId, Operation.READ);
try {
return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus);
return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus, assigneeId);
} catch (Exception e) {
throw handleException(e);
}

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

@ -655,9 +655,7 @@ public abstract class BaseController {
}
AlarmInfo checkAlarmInfoId(AlarmId alarmId, Operation operation) throws ThingsboardException {
return checkEntityId(alarmId, (tenantId, id) -> {
return alarmService.findAlarmInfoByIdAsync(tenantId, alarmId).get();
}, operation);
return checkEntityId(alarmId, alarmService::findAlarmInfoById, operation);
}
AlarmComment checkAlarmCommentId(AlarmCommentId alarmCommentId, AlarmId alarmId) throws ThingsboardException {

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

@ -27,6 +27,7 @@ public class ControllerConstants {
protected static final String EDGE_ID = "edgeId";
protected static final String RPC_ID = "rpcId";
protected static final String ENTITY_ID = "entityId";
protected static final String ASSIGNEE_ID = "assigneeId";
protected static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. " +
"The result is wrapped with PageData object that allows you to iterate over result set using pagination. " +
"See the 'Model' tab of the Response Class for more details. ";
@ -44,6 +45,7 @@ public class ControllerConstants {
protected static final String USER_ID_PARAM_DESCRIPTION = "A string value representing the user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ASSET_ID_PARAM_DESCRIPTION = "A string value representing the asset id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ALARM_ID_PARAM_DESCRIPTION = "A string value representing the alarm id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ASSIGN_ID_PARAM_DESCRIPTION = "A string value representing the user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ALARM_COMMENT_ID_PARAM_DESCRIPTION = "A string value representing the alarm comment id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ENTITY_ID_PARAM_DESCRIPTION = "A string value representing the entity id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";

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

@ -29,6 +29,7 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.query.EntityQueryService;
import org.thingsboard.server.service.security.permission.Operation;
import static org.thingsboard.server.controller.ControllerConstants.ALARM_DATA_QUERY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_COUNT_QUERY_DESCRIPTION;
@ -61,11 +63,7 @@ public class EntityQueryController extends BaseController {
@ApiParam(value = "A JSON value representing the entity count query. See API call notes above for more details.")
@RequestBody EntityCountQuery query) throws ThingsboardException {
checkNotNull(query);
try {
return this.entityQueryService.countEntitiesByQuery(getCurrentUser(), query);
} catch (Exception e) {
throw handleException(e);
}
return this.entityQueryService.countEntitiesByQuery(getCurrentUser(), query);
}
@ApiOperation(value = "Find Entity Data by Query", notes = ENTITY_DATA_QUERY_DESCRIPTION)
@ -76,11 +74,7 @@ public class EntityQueryController extends BaseController {
@ApiParam(value = "A JSON value representing the entity data query. See API call notes above for more details.")
@RequestBody EntityDataQuery query) throws ThingsboardException {
checkNotNull(query);
try {
return this.entityQueryService.findEntityDataByQuery(getCurrentUser(), query);
} catch (Exception e) {
throw handleException(e);
}
return this.entityQueryService.findEntityDataByQuery(getCurrentUser(), query);
}
@ApiOperation(value = "Find Alarms by Query", notes = ALARM_DATA_QUERY_DESCRIPTION)
@ -91,11 +85,12 @@ public class EntityQueryController extends BaseController {
@ApiParam(value = "A JSON value representing the alarm data query. See API call notes above for more details.")
@RequestBody AlarmDataQuery query) throws ThingsboardException {
checkNotNull(query);
try {
return this.entityQueryService.findAlarmDataByQuery(getCurrentUser(), query);
} catch (Exception e) {
throw handleException(e);
checkNotNull(query.getPageLink());
UserId assigneeId = query.getPageLink().getAssigneeId();
if (assigneeId != null) {
checkUserId(assigneeId, Operation.READ);
}
return this.entityQueryService.findAlarmDataByQuery(getCurrentUser(), query);
}
@ApiOperation(value = "Find Entity Keys by Query",
@ -112,15 +107,11 @@ public class EntityQueryController extends BaseController {
@RequestParam("attributes") boolean isAttributes) throws ThingsboardException {
TenantId tenantId = getTenantId();
checkNotNull(query);
try {
EntityDataPageLink pageLink = query.getPageLink();
if (pageLink.getPageSize() > MAX_PAGE_SIZE) {
pageLink.setPageSize(MAX_PAGE_SIZE);
}
return entityQueryService.getKeysByQuery(getCurrentUser(), tenantId, query, isTimeseries, isAttributes);
} catch (Exception e) {
throw handleException(e);
EntityDataPageLink pageLink = query.getPageLink();
if (pageLink.getPageSize() > MAX_PAGE_SIZE) {
pageLink.setPageSize(MAX_PAGE_SIZE);
}
return entityQueryService.getKeysByQuery(getCurrentUser(), tenantId, query, isTimeseries, isAttributes);
}
}

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

@ -87,6 +87,12 @@ public class EntityActionService {
case ALARM_CLEAR:
msgType = DataConstants.ALARM_CLEAR;
break;
case ALARM_ASSIGN:
msgType = DataConstants.ALARM_ASSIGN;
break;
case ALARM_UNASSIGN:
msgType = DataConstants.ALARM_UNASSIGN;
break;
case ALARM_DELETE:
msgType = DataConstants.ALARM_DELETE;
break;

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

@ -48,7 +48,7 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor {
return Futures.immediateFuture(null);
}
try {
Alarm existentAlarm = alarmService.findLatestByOriginatorAndType(tenantId, originatorId, alarmUpdateMsg.getType()).get();
Alarm existentAlarm = alarmService.findLatestActiveByOriginatorAndType(tenantId, originatorId, alarmUpdateMsg.getType());
switch (alarmUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
@ -62,7 +62,9 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor {
existentAlarm.setClearTs(alarmUpdateMsg.getClearTs());
existentAlarm.setPropagate(alarmUpdateMsg.getPropagate());
}
existentAlarm.setStatus(AlarmStatus.valueOf(alarmUpdateMsg.getStatus()));
var alarmStatus = AlarmStatus.valueOf(alarmUpdateMsg.getStatus());
existentAlarm.setCleared(alarmStatus.isCleared());
existentAlarm.setAcknowledged(alarmStatus.isAck());
existentAlarm.setAckTs(alarmUpdateMsg.getAckTs());
existentAlarm.setEndTs(alarmUpdateMsg.getEndTs());
existentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()));
@ -70,18 +72,18 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor {
break;
case ALARM_ACK_RPC_MESSAGE:
if (existentAlarm != null) {
alarmService.ackAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs());
alarmService.acknowledgeAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs());
}
break;
case ALARM_CLEAR_RPC_MESSAGE:
if (existentAlarm != null) {
alarmService.clearAlarm(tenantId, existentAlarm.getId(),
JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()), alarmUpdateMsg.getAckTs());
alarmUpdateMsg.getAckTs(), JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()));
}
break;
case ENTITY_DELETED_RPC_MESSAGE:
if (existentAlarm != null) {
alarmService.deleteAlarm(tenantId, existentAlarm.getId());
alarmService.delAlarm(tenantId, existentAlarm.getId());
}
break;
}

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

@ -74,7 +74,7 @@ public abstract class AbstractTbEntityService {
protected ListenableFuture<Void> removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) {
ListenableFuture<PageData<AlarmInfo>> alarmsFuture =
alarmService.findAlarms(tenantId, new AlarmQuery(entityId, new TimePageLink(Integer.MAX_VALUE), null, null, false));
alarmService.findAlarms(tenantId, new AlarmQuery(entityId, new TimePageLink(Integer.MAX_VALUE), null, null, null, false));
ListenableFuture<List<AlarmId>> alarmIdsFuture = Futures.transform(alarmsFuture, page ->
page.getData().stream().map(AlarmInfo::getId).collect(Collectors.toList()), dbExecutor);

4
application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java

@ -308,6 +308,10 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
return EdgeEventActionType.ALARM_ACK;
case ALARM_CLEAR:
return EdgeEventActionType.ALARM_CLEAR;
case ALARM_ASSIGN:
return EdgeEventActionType.ALARM_ASSIGN;
case ALARM_UNASSIGN:
return EdgeEventActionType.ALARM_UNASSIGN;
case DELETED:
return EdgeEventActionType.DELETED;
case RELATION_ADD_OR_UPDATE:

5
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java

@ -38,8 +38,9 @@ public class DefaultTbAlarmCommentService extends AbstractTbEntityService implem
@Override
public AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException {
ActionType actionType = alarmComment.getId() == null ? ActionType.ADDED_COMMENT : ActionType.UPDATED_COMMENT;
UserId userId = user.getId();
alarmComment.setUserId(userId);
if (user != null) {
alarmComment.setUserId(user.getId());
}
try {
AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment));
notificationEntityService.notifyAlarmComment(alarm, savedAlarmComment, actionType, user);

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

@ -15,9 +15,6 @@
*/
package org.thingsboard.server.service.entitiy.alarm;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -26,13 +23,19 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import java.util.List;
@ -50,9 +53,34 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
ActionType actionType = alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = alarm.getTenantId();
try {
Alarm savedAlarm = checkNotNull(alarmSubscriptionService.createOrUpdateAlarm(alarm));
notificationEntityService.notifyCreateOrUpdateAlarm(savedAlarm, actionType, user);
return savedAlarm;
AlarmApiCallResult result;
if (alarm.getId() == null) {
result = alarmSubscriptionService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, user.getId()));
} else {
result = alarmSubscriptionService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm, user.getId()));
}
if (!result.isSuccessful()) {
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
}
actionType = result.isCreated() ? ActionType.ADDED : ActionType.UPDATED;
if (result.isModified()) {
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), actionType, user);
}
AlarmInfo resultAlarm = result.getAlarm();
if (alarm.isAcknowledged() && !resultAlarm.isAcknowledged()) {
resultAlarm = ack(resultAlarm, alarm.getAckTs(), user);
}
if (alarm.isCleared() && !resultAlarm.isCleared()) {
resultAlarm = clear(resultAlarm, alarm.getClearTs(), user);
}
UserId newAssignee = alarm.getAssigneeId();
UserId curAssignee = resultAlarm.getAssigneeId();
if (newAssignee != null && !newAssignee.equals(curAssignee)) {
resultAlarm = assign(alarm, newAssignee, alarm.getAssignTs(), user);
} else if (newAssignee == null && curAssignee != null) {
resultAlarm = unassign(alarm, alarm.getAssignTs(), user);
}
return new Alarm(resultAlarm);
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ALARM), alarm, actionType, user, e);
throw e;
@ -60,51 +88,126 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
}
@Override
public ListenableFuture<Void> ack(Alarm alarm, User user) {
long ackTs = System.currentTimeMillis();
ListenableFuture<Boolean> future = alarmSubscriptionService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs);
return Futures.transform(future, result -> {
public AlarmInfo ack(Alarm alarm, User user) throws ThingsboardException {
return ack(alarm, System.currentTimeMillis(), user);
}
@Override
public AlarmInfo ack(Alarm alarm, long ackTs, User user) throws ThingsboardException {
AlarmApiCallResult result = alarmSubscriptionService.acknowledgeAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(ackTs));
if (!result.isSuccessful()) {
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
}
if (result.isModified()) {
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was acknowledged by user %s",
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString()))
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString())
.put("subtype", "ACK"))
.build();
try {
alarmCommentService.saveAlarmComment(alarm, alarmComment, user);
} catch (ThingsboardException e) {
log.error("Failed to save alarm comment", e);
}
alarm.setAckTs(ackTs);
alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK);
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ACK, user);
return null;
}, MoreExecutors.directExecutor());
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ACK, user);
} else {
throw new ThingsboardException("Alarm was already acknowledged!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
return result.getAlarm();
}
@Override
public ListenableFuture<Void> clear(Alarm alarm, User user) {
long clearTs = System.currentTimeMillis();
ListenableFuture<Boolean> future = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs);
return Futures.transform(future, result -> {
public AlarmInfo clear(Alarm alarm, User user) throws ThingsboardException {
return clear(alarm, System.currentTimeMillis(), user);
}
@Override
public AlarmInfo clear(Alarm alarm, long clearTs, User user) throws ThingsboardException {
AlarmApiCallResult result = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(clearTs), null);
if (!result.isSuccessful()) {
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
}
if (result.isCleared()) {
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was cleared by user %s",
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString()))
.put("userId", user.getId().toString())
.put("subtype", "CLEAR"))
.build();
try {
alarmCommentService.saveAlarmComment(alarm, alarmComment, user);
} catch (ThingsboardException e) {
log.error("Failed to save alarm comment", e);
}
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_CLEAR, user);
} else {
throw new ThingsboardException("Alarm was already cleared!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
return result.getAlarm();
}
@Override
public AlarmInfo assign(Alarm alarm, UserId assigneeId, long assignTs, User user) throws ThingsboardException {
AlarmApiCallResult result = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, getOrDefault(assignTs));
if (!result.isSuccessful()) {
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
}
AlarmInfo alarmInfo = result.getAlarm();
if (result.isModified()) {
AlarmAssignee assignee = alarmInfo.getAssignee();
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was assigned by user %s to user %s",
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName(),
(assignee.getFirstName() == null || assignee.getLastName() == null) ? assignee.getEmail() : assignee.getFirstName() + " " + assignee.getLastName()))
.put("userId", user.getId().toString())
.put("assigneeId", assignee.getId().toString())
.put("subtype", "ASSIGN"))
.build();
try {
alarmCommentService.saveAlarmComment(alarm, alarmComment, user);
} catch (ThingsboardException e) {
log.error("Failed to save alarm comment", e);
}
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ASSIGN, user);
} else {
throw new ThingsboardException("Alarm was already assigned to this user!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
return alarmInfo;
}
@Override
public AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException {
AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(unassignTs));
if (!result.isSuccessful()) {
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
}
AlarmInfo alarmInfo = result.getAlarm();
if (result.isModified()) {
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was unassigned by user %s",
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString())
.put("subtype", "ASSIGN"))
.build();
try {
alarmCommentService.saveAlarmComment(alarm, alarmComment, user);
} catch (ThingsboardException e) {
log.error("Failed to save alarm comment", e);
}
alarm.setClearTs(clearTs);
alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK);
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user);
return null;
}, MoreExecutors.directExecutor());
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGN, user);
} else {
throw new ThingsboardException("Alarm was already unassigned!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
return alarmInfo;
}
@Override
@ -115,4 +218,8 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
relatedEdgeIds, user, JacksonUtil.toString(alarm));
return alarmSubscriptionService.deleteAlarm(tenantId, alarm.getId());
}
}
private static long getOrDefault(long ts) {
return ts > 0 ? ts : System.currentTimeMillis();
}
}

15
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java

@ -15,18 +15,27 @@
*/
package org.thingsboard.server.service.entitiy.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId;
public interface TbAlarmService {
Alarm save(Alarm entity, User user) throws ThingsboardException;
ListenableFuture<Void> ack(Alarm alarm, User user);
AlarmInfo ack(Alarm alarm, User user) throws ThingsboardException;
ListenableFuture<Void> clear(Alarm alarm, User user);
AlarmInfo ack(Alarm alarm, long ackTs, User user) throws ThingsboardException;
AlarmInfo clear(Alarm alarm, User user) throws ThingsboardException;
AlarmInfo clear(Alarm alarm, long clearTs, User user) throws ThingsboardException;
AlarmInfo assign(Alarm alarm, UserId assigneeId, long assignTs, User user) throws ThingsboardException;
AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException;
Boolean delete(Alarm alarm, User user);
}

2
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -556,7 +556,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
};
private void updateTenantAlarmsCustomer(TenantId tenantId, String name, AtomicLong processed) {
AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(1000), null, null, false);
AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(1000), null, null, null, false);
PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, alarmQuery);
boolean hasNext = true;
while (hasNext) {

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

@ -27,7 +27,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId;
@ -538,13 +538,14 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
subscriptionManagerService.onAlarmUpdate(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), Alarm.class), callback);
JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class),
callback);
} else if (msg.hasAlarmDelete()) {
TbAlarmDeleteProto proto = msg.getAlarmDelete();
subscriptionManagerService.onAlarmDeleted(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), Alarm.class), callback);
JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class), callback);
} else if (msg.hasNotificationUpdate()) {
TransportProtos.NotificationUpdateProto updateProto = msg.getNotificationUpdate();
TenantId tenantId = TenantId.fromUUID(new UUID(updateProto.getTenantIdMSB(), updateProto.getTenantIdLSB()));

3
application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java

@ -228,8 +228,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
if (userCredentials != null && isPositiveInteger(passwordPolicy.getPasswordReuseFrequencyDays())) {
long passwordReuseFrequencyTs = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(passwordPolicy.getPasswordReuseFrequencyDays());
User user = userService.findUserById(tenantId, userCredentials.getUserId());
JsonNode additionalInfo = user.getAdditionalInfo();
JsonNode additionalInfo = userCredentials.getAdditionalInfo();
if (additionalInfo instanceof ObjectNode && additionalInfo.has(UserServiceImpl.USER_PASSWORD_HISTORY)) {
JsonNode userPasswordHistoryJson = additionalInfo.get(UserServiceImpl.USER_PASSWORD_HISTORY);
Map<String, String> userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>() {});

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

@ -26,6 +26,7 @@ import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -41,6 +42,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
@ -258,7 +260,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
updateDeviceInactivityTimeout(tenantId, entityId, attributes);
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes))
new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes))
, null);
}
}
@ -282,7 +284,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
@Override
public void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) {
public void onAlarmUpdate(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback) {
onLocalAlarmSubUpdate(entityId,
s -> {
if (TbSubscriptionType.ALARMS.equals(s.getType())) {
@ -291,16 +293,15 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
return null;
}
},
s -> alarm.getCreatedTime() >= s.getTs(),
s -> alarm,
false
s -> alarm.getCreatedTime() >= s.getTs() || alarm.getAssignTs() >= s.getTs(),
alarm, false
);
notificationRuleProcessingService.process(tenantId, alarm, false);
callback.onSuccess();
}
@Override
public void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) {
public void onAlarmDeleted(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback) {
onLocalAlarmSubUpdate(entityId,
s -> {
if (TbSubscriptionType.ALARMS.equals(s.getType())) {
@ -310,8 +311,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
},
s -> alarm.getCreatedTime() >= s.getTs(),
s -> alarm,
true
alarm, true
);
notificationRuleProcessingService.process(tenantId, alarm, true);
callback.onSuccess();
@ -451,19 +451,20 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
private void onLocalAlarmSubUpdate(EntityId entityId,
Function<TbSubscription, TbAlarmsSubscription> castFunction,
Predicate<TbAlarmsSubscription> filterFunction,
Function<TbAlarmsSubscription, Alarm> processFunction, boolean deleted) {
AlarmInfo alarm, boolean deleted) {
Set<TbSubscription> entitySubscriptions = subscriptionsByEntityId.get(entityId);
if (alarm == null) {
log.warn("[{}] empty alarm update!", entityId);
return;
}
if (entitySubscriptions != null) {
entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> {
Alarm alarm = processFunction.apply(s);
if (alarm != null) {
if (serviceId.equals(s.getServiceId())) {
AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, deleted);
localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY);
} else {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId());
toCoreNotificationsProducer.send(tpi, toProto(s, alarm, deleted), null);
}
if (serviceId.equals(s.getServiceId())) {
AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, deleted);
localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY);
} else {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId());
toCoreNotificationsProducer.send(tpi, toProto(s, alarm, deleted), null);
}
});
} else {
@ -594,12 +595,12 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
});
ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg(
LocalSubscriptionServiceMsgProto.newBuilder().setSubUpdate(builder.build()).build())
LocalSubscriptionServiceMsgProto.newBuilder().setSubUpdate(builder.build()).build())
.build();
return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg);
}
private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, Alarm alarm, boolean deleted) {
private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, AlarmInfo alarm, boolean deleted) {
TbAlarmSubscriptionUpdateProto.Builder builder = TbAlarmSubscriptionUpdateProto.newBuilder();
builder.setSessionId(subscription.getSessionId());
@ -608,8 +609,8 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
builder.setDeleted(deleted);
ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg(
LocalSubscriptionServiceMsgProto.newBuilder()
.setAlarmSubUpdate(builder.build()).build())
LocalSubscriptionServiceMsgProto.newBuilder()
.setAlarmSubUpdate(builder.build()).build())
.build();
return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg);
}

6
application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java

@ -17,12 +17,14 @@ package org.thingsboard.server.service.subscription;
import org.springframework.context.ApplicationListener;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
@ -45,9 +47,9 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List<String> keys, TbCallback callback);
void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
void onAlarmUpdate(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback);
void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
void onAlarmDeleted(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback);
void onNotificationUpdate(TenantId tenantId, UserId recipientId, NotificationUpdate notificationUpdate, TbCallback callback);

27
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -20,7 +20,9 @@ import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.Aggregation;
@ -63,10 +65,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
private final AlarmService alarmService;
@Getter
@Setter
private final LinkedHashMap<EntityId, EntityData> entitiesMap;
@Getter
@Setter
private final HashMap<AlarmId, AlarmData> alarmsMap;
private final int maxEntitiesPerAlarmSubscription;
@ -225,8 +225,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
boolean matchesFilter = filter(alarm);
if (onCurrentPage) {
if (matchesFilter) {
AlarmData updated = new AlarmData(alarm, current.getOriginatorName(), current.getEntityId());
updated.getLatest().putAll(current.getLatest());
AlarmData updated = new AlarmData(subscriptionUpdate.getAlarm(), current);
alarmsMap.put(alarmId, updated);
sendWsMsg(new AlarmDataUpdate(cmdId, null, Collections.singletonList(updated), maxEntitiesPerAlarmSubscription, data.getTotalElements()));
} else {
@ -270,8 +269,24 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
if (filter.getStatusList() != null && !filter.getStatusList().isEmpty()) {
boolean matches = false;
for (AlarmSearchStatus status : filter.getStatusList()) {
if (status.getStatuses().contains(alarm.getStatus())) {
matches = true;
switch (status) {
case ANY:
matches = true;
break;
case ACK:
matches = alarm.isAcknowledged();
break;
case UNACK:
matches = !alarm.isAcknowledged();
break;
case CLEARED:
matches = alarm.isCleared();
break;
case ACTIVE:
matches = !alarm.isCleared();
break;
}
if (matches) {
break;
}
}

10
application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java

@ -17,6 +17,8 @@ package org.thingsboard.server.service.subscription;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
@ -229,8 +231,8 @@ public class TbSubscriptionUtils {
if (proto.getErrorCode() > 0) {
return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg());
} else {
Alarm alarm = JacksonUtil.fromString(proto.getAlarm(), Alarm.class);
return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm);
AlarmInfo alarm = JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class);
return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm, proto.getDeleted());
}
}
@ -356,7 +358,7 @@ public class TbSubscriptionUtils {
return entry;
}
public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, Alarm alarm) {
public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, AlarmInfo alarm) {
TbAlarmUpdateProto.Builder builder = TbAlarmUpdateProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name());
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
@ -369,7 +371,7 @@ public class TbSubscriptionUtils {
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
}
public static ToCoreMsg toAlarmDeletedProto(TenantId tenantId, EntityId entityId, Alarm alarm) {
public static ToCoreMsg toAlarmDeletedProto(TenantId tenantId, EntityId entityId, AlarmInfo alarm) {
TbAlarmDeleteProto.Builder builder = TbAlarmDeleteProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name());
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());

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

@ -22,40 +22,39 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmModificationRequest;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService;
import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.Collection;
import java.util.Optional;
/**
* Created by ashvayka on 27.03.18.
@ -75,6 +74,41 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return "alarm";
}
@Override
public AlarmApiCallResult createAlarm(AlarmCreateOrUpdateActiveRequest request) {
boolean creationEnabled = apiUsageStateService.getApiUsageState(request.getTenantId()).isAlarmCreationEnabled();
var result = alarmService.createAlarm(request, creationEnabled);
if (result.isCreated()) {
apiUsageClient.report(request.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT);
}
return withWsCallback(request, result);
}
@Override
public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
return withWsCallback(alarmService.updateAlarm(request));
}
@Override
public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) {
return withWsCallback(alarmService.acknowledgeAlarm(tenantId, alarmId, ackTs));
}
@Override
public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details) {
return withWsCallback(alarmService.clearAlarm(tenantId, alarmId, clearTs, details));
}
@Override
public AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs) {
return withWsCallback(alarmService.assignAlarm(tenantId, alarmId, assigneeId, assignTs));
}
@Override
public AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs) {
return withWsCallback(alarmService.unassignAlarm(tenantId, alarmId, assignTs));
}
@Override
public Alarm createOrUpdateAlarm(Alarm alarm) {
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled());
@ -102,29 +136,29 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
@Override
public Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId) {
AlarmOperationResult result = alarmService.deleteAlarm(tenantId, alarmId);
AlarmApiCallResult result = alarmService.delAlarm(tenantId, alarmId);
onAlarmDeleted(result);
return result.isSuccessful();
}
@Override
public ListenableFuture<Boolean> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) {
ListenableFuture<AlarmOperationResult> result = alarmService.ackAlarm(tenantId, alarmId, ackTs);
ListenableFuture<AlarmApiCallResult> result = Futures.immediateFuture(alarmService.acknowledgeAlarm(tenantId, alarmId, ackTs));
Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor);
return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor);
return Futures.transform(result, AlarmApiCallResult::isSuccessful, wsCallBackExecutor);
}
@Override
public ListenableFuture<Boolean> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) {
ListenableFuture<AlarmOperationResult> result = clearAlarmForResult(tenantId, alarmId, details, clearTs);
return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor);
AlarmApiCallResult result = alarmService.clearAlarm(tenantId, alarmId, clearTs, details);
return Futures.transform(Futures.immediateFuture(result), AlarmApiCallResult::isSuccessful, wsCallBackExecutor);
}
@Override
public ListenableFuture<AlarmOperationResult> clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) {
ListenableFuture<AlarmOperationResult> result = alarmService.clearAlarm(tenantId, alarmId, details, clearTs);
Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor);
return result;
AlarmApiCallResult result = alarmService.clearAlarm(tenantId, alarmId, clearTs, details);
Futures.addCallback(Futures.immediateFuture(result), new AlarmUpdateCallback(), wsCallBackExecutor);
return Futures.immediateFuture(new AlarmOperationResult(result));
}
@Override
@ -138,8 +172,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
}
@Override
public ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) {
return alarmService.findAlarmInfoByIdAsync(tenantId, alarmId);
public AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId) {
return alarmService.findAlarmInfoById(tenantId, alarmId);
}
@Override
@ -153,8 +187,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
}
@Override
public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) {
return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus);
public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId) {
return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus, assigneeId);
}
@Override
@ -162,15 +196,21 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return alarmService.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds);
}
@Override
public Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
return alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, type);
}
@Override
public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
return alarmService.findLatestByOriginatorAndType(tenantId, originator, type);
}
@Deprecated
private void onAlarmUpdated(AlarmOperationResult result) {
wsCallBackExecutor.submit(() -> {
Alarm alarm = result.getAlarm();
TenantId tenantId = result.getAlarm().getTenantId();
AlarmInfo alarm = new AlarmInfo(result.getAlarm());
TenantId tenantId = alarm.getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY);
@ -181,10 +221,24 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
});
}
private void onAlarmDeleted(AlarmOperationResult result) {
private void onAlarmUpdated(AlarmApiCallResult result) {
wsCallBackExecutor.submit(() -> {
Alarm alarm = result.getAlarm();
TenantId tenantId = result.getAlarm().getTenantId();
AlarmInfo alarm = result.getAlarm();
TenantId tenantId = alarm.getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY);
}, () -> {
return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm);
});
}
});
}
private void onAlarmDeleted(AlarmApiCallResult result) {
wsCallBackExecutor.submit(() -> {
AlarmInfo alarm = result.getAlarm();
TenantId tenantId = alarm.getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAlarmDeleted(tenantId, entityId, alarm, TbCallback.EMPTY);
@ -195,9 +249,9 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
});
}
private class AlarmUpdateCallback implements FutureCallback<AlarmOperationResult> {
private class AlarmUpdateCallback implements FutureCallback<AlarmApiCallResult> {
@Override
public void onSuccess(@Nullable AlarmOperationResult result) {
public void onSuccess(@Nullable AlarmApiCallResult result) {
onAlarmUpdated(result);
}
@ -207,4 +261,31 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
}
}
private AlarmApiCallResult withWsCallback(AlarmApiCallResult result) {
return withWsCallback(null, result);
}
private AlarmApiCallResult withWsCallback(AlarmModificationRequest request, AlarmApiCallResult result) {
if (result.isSuccessful() && result.isModified()) {
Futures.addCallback(Futures.immediateFuture(result), new AlarmUpdateCallback(), wsCallBackExecutor);
if (result.isSeverityChanged()) {
AlarmInfo alarm = result.getAlarm();
AlarmComment.AlarmCommentBuilder alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text",
String.format("Alarm severity was updated from %s to %s", result.getOldSeverity(), alarm.getSeverity())));
if (request != null && request.getUserId() != null) {
alarmComment.userId(request.getUserId());
}
try {
alarmCommentService.saveAlarmComment(alarm, alarmComment.build(), null);
} catch (ThingsboardException e) {
log.error("Failed to save alarm comment", e);
}
}
}
return result;
}
}

1
application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java

@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId;

14
application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.ws.telemetry.sub;
import lombok.Getter;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.service.subscription.SubscriptionErrorCode;
public class AlarmSubscriptionUpdate {
@ -28,15 +28,15 @@ public class AlarmSubscriptionUpdate {
@Getter
private String errorMsg;
@Getter
private Alarm alarm;
private AlarmInfo alarm;
@Getter
private boolean alarmDeleted;
public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm) {
public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarm) {
this(subscriptionId, alarm, false);
}
public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, boolean alarmDeleted) {
public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarm, boolean alarmDeleted) {
super();
this.subscriptionId = subscriptionId;
this.alarm = alarm;
@ -56,7 +56,7 @@ public class AlarmSubscriptionUpdate {
@Override
public String toString() {
return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", alarm="
+ alarm + "]";
return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg +
", alarm=" + alarm + "]";
}
}
}

4
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -172,6 +172,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected TenantId differentTenantId;
protected CustomerId differentCustomerId;
protected UserId customerUserId;
protected UserId differentCustomerUserId;
@SuppressWarnings("rawtypes")
private HttpMessageConverter mappingJackson2HttpMessageConverter;
@ -371,7 +372,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
differentCustomerUser.setEmail(DIFFERENT_CUSTOMER_USER_EMAIL);
createUserAndLogin(differentCustomerUser, DIFFERENT_CUSTOMER_USER_PASSWORD);
differentCustomerUser = createUserAndLogin(differentCustomerUser, DIFFERENT_CUSTOMER_USER_PASSWORD);
differentCustomerUserId = differentCustomerUser.getId();
}
}

2
application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java

@ -79,7 +79,6 @@ public abstract class BaseAlarmCommentControllerTest extends AbstractControllerT
.tenantId(tenantId)
.customerId(customerId)
.originator(customerDevice.getId())
.status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type("test alarm type")
.build();
@ -316,7 +315,6 @@ public abstract class BaseAlarmCommentControllerTest extends AbstractControllerT
Alarm alarm = Alarm.builder()
.originator(device.getId())
.status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type("Test")
.build();

204
application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java

@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
@ -124,7 +125,8 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
testNotifyEntityAllOneTime(updatedAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
testNotifyEntityAllOneTime(foundAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.UPDATED);
}
@ -140,8 +142,57 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
testNotifyEntityAllOneTime(updatedAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UPDATED);
alarm = updatedAlarm;
alarm.setAcknowledged(true);
alarm.setAckTs(System.currentTimeMillis() - 1000);
updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(updatedAlarm);
Assert.assertTrue(updatedAlarm.isAcknowledged());
Assert.assertEquals(alarm.getAckTs(), updatedAlarm.getAckTs());
foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ACK);
alarm = updatedAlarm;
alarm.setCleared(true);
alarm.setClearTs(System.currentTimeMillis() - 1000);
updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(updatedAlarm);
Assert.assertTrue(updatedAlarm.isCleared());
Assert.assertEquals(alarm.getClearTs(), updatedAlarm.getClearTs());
foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_CLEAR);
alarm = updatedAlarm;
alarm.setAssigneeId(tenantAdminUserId);
alarm.setAssignTs(System.currentTimeMillis() - 1000);
updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(tenantAdminUserId, updatedAlarm.getAssigneeId());
Assert.assertEquals(alarm.getAssignTs(), updatedAlarm.getAssignTs());
foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
alarm = updatedAlarm;
alarm.setAssigneeId(null);
alarm.setAssignTs(System.currentTimeMillis() - 1000);
updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(updatedAlarm);
Assert.assertNull(updatedAlarm.getAssigneeId());
Assert.assertEquals(alarm.getAssignTs(), updatedAlarm.getAssignTs());
foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGN);
}
@Test
@ -187,7 +238,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
testNotifyEntityOneTimeMsgToEdgeServiceNever(new Alarm(alarm), alarm.getId(), alarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.DELETED);
}
@ -200,7 +251,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
testNotifyEntityOneTimeMsgToEdgeServiceNever(new Alarm(alarm), alarm.getId(), alarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED);
}
@ -245,7 +296,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
@ -261,7 +312,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
@ -278,7 +329,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.ACTIVE_ACK, foundAlarm.getStatus());
@ -348,6 +399,129 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
.andExpect(statusReason(containsString(msgErrorPermission)));
}
@Test
public void testAssignAlarm() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
long beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
}
@Test
public void testAssignAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isForbidden());
}
@Test
public void testReassignAlarm() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
long beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
logout();
loginCustomerUser();
Mockito.reset(tbClusterService, auditLogService);
beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarm.getId() + "/assign/" + customerUserId.getId()).andExpect(status().isOk());
foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(customerUserId, foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_ASSIGN);
}
@Test
public void testUnassignAlarm() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
long beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
beforeAssignmentTs = System.currentTimeMillis();
doDelete("/api/alarm/" + alarm.getId() + "/assign").andExpect(status().isOk());
foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertNull(foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGN);
}
@Test
public void testUnassignTenantAlarmViaCustomer() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
long beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
logout();
loginCustomerUser();
Mockito.reset(tbClusterService, auditLogService);
beforeAssignmentTs = System.currentTimeMillis();
doDelete("/api/alarm/" + alarm.getId() + "/assign").andExpect(status().isOk());
foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertNull(foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_UNASSIGN);
}
@Test
public void testFindAlarmsViaCustomerUser() throws Exception {
loginCustomerUser();
@ -364,7 +538,8 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
var response = doGetTyped(
"/api/alarm/" + EntityType.DEVICE + "/"
+ customerDevice.getUuidId() + "?page=0&pageSize=" + size,
new TypeReference<PageData<AlarmInfo>>() {}
new TypeReference<PageData<AlarmInfo>>() {
}
);
var foundAlarmInfos = response.getData();
Assert.assertNotNull("Found pageData is null", foundAlarmInfos);
@ -412,7 +587,6 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Alarm alarm = Alarm.builder()
.originator(device.getId())
.status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type("Test")
.build();
@ -431,7 +605,8 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
this.token = tokens.get("token").asText();
PageData<AlarmInfo> pageData = doGetTyped(
"/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=1", new TypeReference<PageData<AlarmInfo>>() {}
"/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=1", new TypeReference<PageData<AlarmInfo>>() {
}
);
Assert.assertNotNull("Found pageData is null", pageData);
@ -457,12 +632,11 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
testEntityDaoWithRelationsTransactionalException(alarmDao, customerDevice.getId(), alarmId, "/api/alarm/" + alarmId);
}
private Alarm createAlarm(String type) throws Exception {
private AlarmInfo createAlarm(String type) throws Exception {
Alarm alarm = Alarm.builder()
.tenantId(tenantId)
.customerId(customerId)
.originator(customerDevice.getId())
.status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type(type)
.build();
@ -470,6 +644,10 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(alarm);
return alarm;
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(alarm, new Alarm(foundAlarm));
return foundAlarm;
}
}

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

@ -76,7 +76,6 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest {
Device device = findDeviceByName("Edge Device 1");
Alarm alarm = new Alarm();
alarm.setOriginator(device.getId());
alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
alarm.setType("alarm");
alarm.setSeverity(AlarmSeverity.CRITICAL);
edgeImitator.expectMessageAmount(1);

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

@ -27,9 +27,10 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService;
@ -81,36 +82,41 @@ public class DefaultTbAlarmServiceTest {
@Test
public void testSave() throws ThingsboardException {
var alarm = new Alarm();
when(alarmSubscriptionService.createOrUpdateAlarm(alarm)).thenReturn(alarm);
var alarm = new AlarmInfo();
when(alarmSubscriptionService.createAlarm(any())).thenReturn(AlarmApiCallResult.builder()
.successful(true)
.modified(true)
.alarm(alarm)
.build());
service.save(alarm, new User());
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any());
verify(alarmSubscriptionService, times(1)).createOrUpdateAlarm(eq(alarm));
verify(alarmSubscriptionService, times(1)).createAlarm(any());
}
@Test
public void testAck() {
public void testAck() throws ThingsboardException {
var alarm = new Alarm();
alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
when(alarmSubscriptionService.ackAlarm(any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true));
when(alarmSubscriptionService.acknowledgeAlarm(any(), any(), anyLong()))
.thenReturn(AlarmApiCallResult.builder().successful(true).modified(true).build());
service.ack(alarm, new User(new UserId(UUID.randomUUID())));
verify(alarmCommentService, times(1)).createOrUpdateAlarmComment(any(), any());
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any());
verify(alarmSubscriptionService, times(1)).ackAlarm(any(), any(), anyLong());
verify(alarmSubscriptionService, times(1)).acknowledgeAlarm(any(), any(), anyLong());
}
@Test
public void testClear() {
public void testClear() throws ThingsboardException {
var alarm = new Alarm();
alarm.setStatus(AlarmStatus.ACTIVE_ACK);
when(alarmSubscriptionService.clearAlarm(any(), any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true));
alarm.setAcknowledged(true);
when(alarmSubscriptionService.clearAlarm(any(), any(), anyLong(), any()))
.thenReturn(AlarmApiCallResult.builder().successful(true).cleared(true).build());
service.clear(alarm, new User(new UserId(UUID.randomUUID())));
verify(alarmCommentService, times(1)).createOrUpdateAlarmComment(any(), any());
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any());
verify(alarmSubscriptionService, times(1)).clearAlarm(any(), any(), any(), anyLong());
verify(alarmSubscriptionService, times(1)).clearAlarm(any(), any(), anyLong(), any());
}
@Test

85
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java

@ -0,0 +1,85 @@
/**
* 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.alarm;
import lombok.Builder;
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 java.util.List;
@Data
public class AlarmApiCallResult {
private final boolean successful;
private final boolean created;
private final boolean modified;
private final boolean cleared;
private final AlarmInfo alarm;
private final Alarm old;
private final List<EntityId> propagatedEntitiesList;
@Builder
private AlarmApiCallResult(boolean successful, boolean created, boolean modified, boolean cleared, AlarmInfo alarm, Alarm old, List<EntityId> propagatedEntitiesList) {
this.successful = successful;
this.created = created;
this.modified = modified;
this.cleared = cleared;
this.alarm = alarm;
this.old = old;
this.propagatedEntitiesList = propagatedEntitiesList;
}
public AlarmApiCallResult(AlarmApiCallResult other, List<EntityId> propagatedEntitiesList) {
this.successful = other.successful;
this.created = other.created;
this.modified = other.modified;
this.cleared = other.cleared;
this.alarm = other.alarm;
this.old = other.old;
this.propagatedEntitiesList = propagatedEntitiesList;
}
public boolean isSeverityChanged() {
if (alarm == null || old == null) {
return false;
} else {
return !alarm.getSeverity().equals(old.getSeverity());
}
}
public AlarmSeverity getOldSeverity() {
return isSeverityChanged() ? old.getSeverity() : null;
}
public boolean isPropagationChanged() {
if (created) {
return true;
}
if (alarm == null || old == null) {
return false;
}
return (alarm.isPropagate() != old.isPropagate()) ||
(alarm.isPropagateToOwner() != old.isPropagateToOwner()) ||
(alarm.isPropagateToTenant() != old.isPropagateToTenant()) ||
(!alarm.getPropagateRelationTypes().equals(old.getPropagateRelationTypes()));
}
}

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

@ -16,16 +16,20 @@
package org.thingsboard.server.dao.alarm;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Collections;
import java.util.List;
@Builder
@Data
@AllArgsConstructor
@Deprecated
public class AlarmOperationResult {
private final Alarm alarm;
private final boolean successful;
@ -40,4 +44,21 @@ public class AlarmOperationResult {
public AlarmOperationResult(Alarm alarm, boolean successful, List<EntityId> propagatedEntitiesList) {
this(alarm, successful, false, null, propagatedEntitiesList);
}
public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List<EntityId> propagatedEntitiesList) {
this.alarm = alarm;
this.successful = successful;
this.created = created;
this.propagatedEntitiesList = propagatedEntitiesList;
this.oldSeverity = null;
}
//Temporary while we have not removed the AlarmOperationResult.
public AlarmOperationResult(AlarmApiCallResult result) {
this.alarm = result.getAlarm() != null ? new Alarm(result.getAlarm()) : null;
this.successful = result.isSuccessful() && (result.isCreated() || result.isModified());
this.created = result.isCreated();
this.oldSeverity = result.getOldSeverity();
this.propagatedEntitiesList = result.getPropagatedEntitiesList();
}
}

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

@ -23,10 +23,13 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
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.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
@ -34,35 +37,76 @@ import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.Collection;
/**
* Created by ashvayka on 11.05.17.
*/
public interface AlarmService extends EntityDaoService {
/*
* New API, since 3.5.
*/
/**
* Designed for atomic operations over active alarms.
* Only one active alarm may exist for the pair {originatorId, alarmType}
*/
AlarmApiCallResult createAlarm(AlarmCreateOrUpdateActiveRequest request);
/**
* Designed for atomic operations over active alarms.
* Only one active alarm may exist for the pair {originatorId, alarmType}
*/
AlarmApiCallResult createAlarm(AlarmCreateOrUpdateActiveRequest request, boolean alarmCreationEnabled);
/**
* Designed to update existing alarm. Accepts only part of the alarm fields.
*/
AlarmApiCallResult updateAlarm(AlarmUpdateRequest request);
AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details);
AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long ts);
AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long ts);
AlarmApiCallResult delAlarm(TenantId tenantId, AlarmId alarmId);
/*
* Legacy API, before 3.5.
*/
@Deprecated(since = "3.5.0", forRemoval = true)
AlarmOperationResult createOrUpdateAlarm(Alarm alarm);
@Deprecated(since = "3.5.0", forRemoval = true)
AlarmOperationResult createOrUpdateAlarm(Alarm alarm, boolean alarmCreationEnabled);
AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId);
@Deprecated(since = "3.5.0", forRemoval = true)
ListenableFuture<AlarmOperationResult> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
@Deprecated(since = "3.5.0", forRemoval = true)
ListenableFuture<AlarmOperationResult> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
@Deprecated(since = "3.5.0", forRemoval = true)
AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId);
@Deprecated(since = "3.5.0", forRemoval = true)
ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
// Other API
Alarm findAlarmById(TenantId tenantId, AlarmId alarmId);
ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId);
ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId);
AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId);
ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query);
ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query);
AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus,
AlarmStatus alarmStatus);
AlarmStatus alarmStatus, String assigneeId);
ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId,
AlarmDataQuery query, Collection<EntityId> orderedEntityIds);

6
common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.entity;
import org.thingsboard.server.common.data.id.NameLabelAndCustomerDetails;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -29,10 +30,13 @@ public interface EntityService {
Optional<String> fetchEntityName(TenantId tenantId, EntityId entityId);
Optional<String> fetchEntityLabel(TenantId tenantId, EntityId entityId);
Optional<CustomerId> fetchEntityCustomerId(TenantId tenantId, EntityId entityId);
Optional<NameLabelAndCustomerDetails> fetchNameLabelAndCustomerDetails(TenantId tenantId, EntityId entityId);
long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query);
PageData<EntityData> findEntityDataByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query);
}

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

@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName {
public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasEmail {
private static final long serialVersionUID = 5047448057830660988L;

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

@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public class Customer extends ContactBased<CustomerId> implements HasTenantId, ExportableEntity<CustomerId> {
public class Customer extends ContactBased<CustomerId> implements HasTenantId, ExportableEntity<CustomerId>, HasTitle {
private static final long serialVersionUID = -1599722990298929275L;

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

@ -31,7 +31,7 @@ import java.util.Objects;
import java.util.Set;
@ApiModel
public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName, HasTenantId {
public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName, HasTenantId, HasTitle {
private TenantId tenantId;
@NoXss

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

@ -74,6 +74,8 @@ public class DataConstants {
public static final String TIMESERIES_DELETED = "TIMESERIES_DELETED";
public static final String ALARM_ACK = "ALARM_ACK";
public static final String ALARM_CLEAR = "ALARM_CLEAR";
public static final String ALARM_ASSIGN = "ALARM_ASSIGN";
public static final String ALARM_UNASSIGN = "ALARM_UNASSIGN";
public static final String ALARM_DELETE = "ALARM_DELETE";
public static final String COMMENT_CREATED = "COMMENT_CREATED";
public static final String COMMENT_UPDATED = "COMMENT_UPDATED";

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

@ -40,7 +40,7 @@ import java.util.Optional;
@ApiModel
@EqualsAndHashCode(callSuper = true)
@Slf4j
public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasName, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity<DeviceId> {
public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity<DeviceId> {
private static final long serialVersionUID = 2807343040519543363L;

29
common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java

@ -19,7 +19,30 @@ package org.thingsboard.server.common.data;
* @author Andrew Shvayka
*/
public enum EntityType {
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE,
TENANT_PROFILE, DEVICE_PROFILE, ASSET_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC, QUEUE,
NOTIFICATION_TARGET, NOTIFICATION_TEMPLATE, NOTIFICATION_REQUEST, NOTIFICATION, NOTIFICATION_RULE;
TENANT,
CUSTOMER,
USER,
DASHBOARD,
ASSET,
DEVICE,
ALARM,
RULE_CHAIN,
RULE_NODE,
ENTITY_VIEW,
WIDGETS_BUNDLE,
WIDGET_TYPE,
TENANT_PROFILE,
DEVICE_PROFILE,
ASSET_PROFILE,
API_USAGE_STATE,
TB_RESOURCE,
OTA_PACKAGE,
EDGE,
RPC,
QUEUE,
NOTIFICATION_TARGET,
NOTIFICATION_TEMPLATE,
NOTIFICATION_REQUEST,
NOTIFICATION,
NOTIFICATION_RULE
}

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

@ -0,0 +1,22 @@
/**
* 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;
public interface HasEmail extends HasName {
String getEmail();
}

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

@ -0,0 +1,22 @@
/**
* 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;
public interface HasLabel extends HasName {
String getLabel();
}

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

@ -0,0 +1,22 @@
/**
* 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;
public interface HasTitle {
String getTitle();
}

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

@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.ota.OtaPackageType;
@Slf4j
@Data
@EqualsAndHashCode(callSuper = true)
public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackageId> implements HasName, HasTenantId {
public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackageId> implements HasName, HasTenantId, HasTitle {
private static final long serialVersionUID = 3168391583570815419L;

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

@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel
@EqualsAndHashCode(callSuper = true)
public class Tenant extends ContactBased<TenantId> implements HasTenantId {
public class Tenant extends ContactBased<TenantId> implements HasTenantId, HasTitle {
private static final long serialVersionUID = 8057243243859922101L;

55
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.alarm;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModel;
@ -22,6 +23,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasName;
@ -30,6 +32,7 @@ import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@ -40,8 +43,10 @@ import java.util.List;
*/
@ApiModel
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, HasCustomerId {
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@ -58,25 +63,31 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
private EntityId originator;
@ApiModelProperty(position = 8, required = true, value = "Alarm severity", example = "CRITICAL")
private AlarmSeverity severity;
@ApiModelProperty(position = 9, required = true, value = "Alarm status", example = "CLEARED_UNACK")
private AlarmStatus status;
@ApiModelProperty(position = 10, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565")
@ApiModelProperty(position = 9, required = true, value = "Acknowledged", example = "true")
private boolean acknowledged;
@ApiModelProperty(position = 10, required = true, value = "Cleared", example = "false")
private boolean cleared;
@ApiModelProperty(position = 11, value = "Alarm assignee user id")
private UserId assigneeId;
@ApiModelProperty(position = 12, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565")
private long startTs;
@ApiModelProperty(position = 11, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
@ApiModelProperty(position = 13, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
private long endTs;
@ApiModelProperty(position = 12, value = "Timestamp of the alarm acknowledgement, in milliseconds", example = "1634115221948")
@ApiModelProperty(position = 14, value = "Timestamp of the alarm acknowledgement, in milliseconds", example = "1634115221948")
private long ackTs;
@ApiModelProperty(position = 13, value = "Timestamp of the alarm clearing, in milliseconds", example = "1634114528465")
@ApiModelProperty(position = 15, value = "Timestamp of the alarm clearing, in milliseconds", example = "1634114528465")
private long clearTs;
@ApiModelProperty(position = 14, value = "JSON object with alarm details")
@ApiModelProperty(position = 16, value = "Timestamp of the alarm assignment, in milliseconds", example = "1634115928465")
private long assignTs;
@ApiModelProperty(position = 17, value = "JSON object with alarm details")
private transient JsonNode details;
@ApiModelProperty(position = 15, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true")
@ApiModelProperty(position = 18, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true")
private boolean propagate;
@ApiModelProperty(position = 16, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true")
@ApiModelProperty(position = 19, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true")
private boolean propagateToOwner;
@ApiModelProperty(position = 17, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true")
@ApiModelProperty(position = 20, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true")
private boolean propagateToTenant;
@ApiModelProperty(position = 18, value = "JSON array of relation types that should be used for propagation. " +
@ApiModelProperty(position = 21, value = "JSON array of relation types that should be used for propagation. " +
"By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " +
"This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.")
private List<String> propagateRelationTypes;
@ -97,11 +108,14 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
this.type = alarm.getType();
this.originator = alarm.getOriginator();
this.severity = alarm.getSeverity();
this.status = alarm.getStatus();
this.assigneeId = alarm.getAssigneeId();
this.startTs = alarm.getStartTs();
this.endTs = alarm.getEndTs();
this.acknowledged = alarm.isAcknowledged();
this.ackTs = alarm.getAckTs();
this.clearTs = alarm.getClearTs();
this.cleared = alarm.isCleared();
this.assignTs = alarm.getAssignTs();
this.details = alarm.getDetails();
this.propagate = alarm.isPropagate();
this.propagateToOwner = alarm.isPropagateToOwner();
@ -119,7 +133,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
@ApiModelProperty(position = 1, value = "JSON object with the alarm Id. " +
"Specify this field to update the alarm. " +
"Referencing non-existing alarm Id will cause error. " +
"Omit this field to create new alarm." )
"Omit this field to create new alarm.")
@Override
public AlarmId getId() {
return super.getId();
@ -132,4 +146,19 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
return super.getCreatedTime();
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@ApiModelProperty(position = 22, required = true, value = "status of the Alarm", example = "ACTIVE_UNACK", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public AlarmStatus getStatus() {
return toStatus(cleared, acknowledged);
}
public static AlarmStatus toStatus(boolean cleared, boolean acknowledged) {
if (cleared) {
return acknowledged ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK;
} else {
return acknowledged ? AlarmStatus.ACTIVE_ACK : AlarmStatus.ACTIVE_UNACK;
}
}
}

37
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java

@ -0,0 +1,37 @@
/**
* 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.alarm;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.UserId;
import java.io.Serializable;
@Builder
@AllArgsConstructor
@Data
public class AlarmAssignee implements Serializable {
private static final long serialVersionUID = 6628286223963972860L;
private final UserId id;
private final String firstName;
private final String lastName;
private final String email;
}

30
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssigneeUpdate.java

@ -0,0 +1,30 @@
/**
* 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.alarm;
import lombok.Data;
import java.io.Serializable;
@Data
public class AlarmAssigneeUpdate implements Serializable {
private static final long serialVersionUID = -2391676304697483808L;
private final boolean deleted;
private final AlarmAssignee assignee;
}

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

@ -0,0 +1,87 @@
/**
* 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.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Data
@Builder
public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationRequest {
@NotNull
@ApiModelProperty(position = 1, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId;
@ApiModelProperty(position = 2, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private CustomerId customerId;
@NotNull
@ApiModelProperty(position = 3, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm")
@Length(fieldName = "type")
private String type;
@NotNull
@ApiModelProperty(position = 4, required = true, value = "JSON object with alarm originator id")
private EntityId originator;
@NotNull
@ApiModelProperty(position = 5, required = true, value = "Alarm severity", example = "CRITICAL")
private AlarmSeverity severity;
@ApiModelProperty(position = 6, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565")
private long startTs;
@ApiModelProperty(position = 7, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
private long endTs;
@NoXss
@ApiModelProperty(position = 8, value = "JSON object with alarm details")
private JsonNode details;
@Valid
@ApiModelProperty(position = 9, value = "JSON object with propagation details")
private AlarmPropagationInfo propagation;
private UserId userId;
public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a) {
return fromAlarm(a, null);
}
public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a, UserId userId) {
return AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(a.getTenantId())
.customerId(a.getCustomerId())
.type(a.getType())
.originator(a.getOriginator())
.severity((a.getSeverity()))
.startTs(a.getStartTs())
.endTs(a.getEndTs())
.details(a.getDetails())
.propagation(AlarmPropagationInfo.builder()
.propagate(a.isPropagate())
.propagateToOwner(a.isPropagateToOwner())
.propagateToTenant(a.isPropagateToTenant())
.propagateRelationTypes(a.getPropagateRelationTypes()).build())
.userId(userId)
.build();
}
}

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

@ -17,15 +17,36 @@ package org.thingsboard.server.common.data.alarm;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.thingsboard.server.common.data.User;
import java.util.Objects;
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@ApiModel
public class AlarmInfo extends Alarm {
private static final long serialVersionUID = 2807343093519543363L;
@Getter
@Setter
@ApiModelProperty(position = 19, value = "Alarm originator name", example = "Thermostat")
private String originatorName;
@Getter
@Setter
@ApiModelProperty(position = 20, value = "Alarm originator label", example = "Thermostat label")
private String originatorLabel;
@Getter
@Setter
@ApiModelProperty(position = 21, value = "Alarm assignee")
private AlarmAssignee assignee;
public AlarmInfo() {
super();
}
@ -34,35 +55,18 @@ public class AlarmInfo extends Alarm {
super(alarm);
}
public AlarmInfo(Alarm alarm, String originatorName) {
super(alarm);
this.originatorName = originatorName;
}
public String getOriginatorName() {
return originatorName;
public AlarmInfo(AlarmInfo alarmInfo) {
super(alarmInfo);
this.originatorName = alarmInfo.originatorName;
this.originatorLabel = alarmInfo.originatorLabel;
this.assignee = alarmInfo.getAssignee();
}
public void setOriginatorName(String originatorName) {
public AlarmInfo(Alarm alarm, String originatorName, String originatorLabel, AlarmAssignee assignee) {
super(alarm);
this.originatorName = originatorName;
this.originatorLabel = originatorLabel;
this.assignee = assignee;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
AlarmInfo alarmInfo = (AlarmInfo) o;
return originatorName != null ? originatorName.equals(alarmInfo.originatorName) : alarmInfo.originatorName == null;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (originatorName != null ? originatorName.hashCode() : 0);
return result;
}
}

34
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.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.common.data.alarm;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
public interface AlarmModificationRequest {
TenantId getTenantId();
long getStartTs();
long getEndTs();
void setStartTs(long startTs);
void setEndTs(long endTs);
UserId getUserId();
}

46
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java

@ -0,0 +1,46 @@
/**
* 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.alarm;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.Collections;
import java.util.List;
@Builder
@Data
public class AlarmPropagationInfo {
public static AlarmPropagationInfo EMPTY = new AlarmPropagationInfo(false, false, false, Collections.emptyList());
@ApiModelProperty(position = 1, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true")
private boolean propagate;
@ApiModelProperty(position = 2, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true")
private boolean propagateToOwner;
@ApiModelProperty(position = 3, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true")
private boolean propagateToTenant;
@NoXss
@ApiModelProperty(position = 4, value = "JSON array of relation types that should be used for propagation. " +
"By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " +
"This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.")
private List<String> propagateRelationTypes;
}

3
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java

@ -19,6 +19,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageLink;
/**
@ -33,6 +34,8 @@ public class AlarmQuery {
private TimePageLink pageLink;
private AlarmSearchStatus searchStatus;
private AlarmStatus status;
private UserId assigneeId;
@Deprecated
private Boolean fetchOriginator;
}

24
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSearchStatus.java

@ -15,26 +15,12 @@
*/
package org.thingsboard.server.common.data.alarm;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
public enum AlarmSearchStatus {
ANY(AlarmStatus.values()),
ACTIVE(AlarmStatus.ACTIVE_ACK, AlarmStatus.ACTIVE_UNACK),
CLEARED(AlarmStatus.CLEARED_ACK, AlarmStatus.CLEARED_UNACK),
ACK(AlarmStatus.ACTIVE_ACK, AlarmStatus.CLEARED_ACK),
UNACK(AlarmStatus.ACTIVE_UNACK, AlarmStatus.CLEARED_UNACK);
@JsonIgnore
@Getter
private Set<AlarmStatus> statuses;
ANY,
ACTIVE,
CLEARED,
ACK,
UNACK;
AlarmSearchStatus(AlarmStatus... statuses) {
this.statuses = new LinkedHashSet<>(Arrays.asList(statuses));
}
}

118
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatusFilter.java

@ -0,0 +1,118 @@
/**
* 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.alarm;
import java.util.List;
import java.util.Optional;
public class AlarmStatusFilter {
private static final AlarmStatusFilter EMPTY = new AlarmStatusFilter(Optional.empty(), Optional.empty());
private final Optional<Boolean> clearFilter;
private final Optional<Boolean> ackFilter;
private AlarmStatusFilter(Optional<Boolean> clearFilter, Optional<Boolean> ackFilter) {
this.clearFilter = clearFilter;
this.ackFilter = ackFilter;
}
public static AlarmStatusFilter from(AlarmQuery query) {
if (query.getSearchStatus() != null) {
return AlarmStatusFilter.from(query.getSearchStatus());
} else if (query.getStatus() != null) {
return AlarmStatusFilter.from(query.getStatus());
}
return AlarmStatusFilter.empty();
}
public static AlarmStatusFilter from(AlarmSearchStatus alarmSearchStatus) {
switch (alarmSearchStatus) {
case ACK:
return new AlarmStatusFilter(Optional.empty(), Optional.of(true));
case UNACK:
return new AlarmStatusFilter(Optional.empty(), Optional.of(false));
case ACTIVE:
return new AlarmStatusFilter(Optional.of(false), Optional.empty());
case CLEARED:
return new AlarmStatusFilter(Optional.of(true), Optional.empty());
default:
return EMPTY;
}
}
public static AlarmStatusFilter from(AlarmStatus alarmStatus) {
switch (alarmStatus) {
case ACTIVE_UNACK:
return new AlarmStatusFilter(Optional.of(false), Optional.of(false));
case ACTIVE_ACK:
return new AlarmStatusFilter(Optional.of(false), Optional.of(true));
case CLEARED_UNACK:
return new AlarmStatusFilter(Optional.of(true), Optional.of(false));
case CLEARED_ACK:
return new AlarmStatusFilter(Optional.of(true), Optional.of(true));
default:
return EMPTY;
}
}
public static AlarmStatusFilter empty() {
return EMPTY;
}
public boolean hasAnyFilter() {
return clearFilter.isPresent() || ackFilter.isPresent();
}
public boolean hasClearFilter() {
return clearFilter.isPresent();
}
public boolean hasAckFilter() {
return ackFilter.isPresent();
}
public boolean getClearFilter() {
return clearFilter.orElseThrow(() -> new RuntimeException("Clear filter is not set! Use `hasClearFilter` to check."));
}
public boolean getAckFilter() {
return ackFilter.orElseThrow(() -> new RuntimeException("Ack filter is not set! Use `hasAckFilter` to check."));
}
public static AlarmStatusFilter fromList(List<AlarmSearchStatus> list) {
if (list == null || list.isEmpty() || list.contains(AlarmSearchStatus.ANY)) {
return EMPTY;
}
boolean clearFilter = list.contains(AlarmSearchStatus.CLEARED);
boolean activeFilter = list.contains(AlarmSearchStatus.ACTIVE);
Optional<Boolean> clear = Optional.empty();
if (clearFilter && !activeFilter || !clearFilter && activeFilter) {
clear = Optional.of(clearFilter);
}
boolean ackFilter = list.contains(AlarmSearchStatus.ACK);
boolean unackFilter = list.contains(AlarmSearchStatus.UNACK);
Optional<Boolean> ack = Optional.empty();
if (ackFilter && !unackFilter || !ackFilter && unackFilter) {
ack = Optional.of(ackFilter);
}
return new AlarmStatusFilter(clear, ack);
}
}

79
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java

@ -0,0 +1,79 @@
/**
* 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.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Data
@Builder
public class AlarmUpdateRequest implements AlarmModificationRequest {
@NotNull
@ApiModelProperty(position = 1, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId;
@NotNull
@ApiModelProperty(position = 2, value = "JSON object with the alarm Id. " +
"Specify this field to update the alarm. " +
"Referencing non-existing alarm Id will cause error. " +
"Omit this field to create new alarm.")
private AlarmId alarmId;
@NotNull
@ApiModelProperty(position = 3, required = true, value = "Alarm severity", example = "CRITICAL")
private AlarmSeverity severity;
@ApiModelProperty(position = 4, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565")
private long startTs;
@ApiModelProperty(position = 5, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
private long endTs;
@NoXss
@ApiModelProperty(position = 6, value = "JSON object with alarm details")
private JsonNode details;
@Valid
@ApiModelProperty(position = 7, value = "JSON object with propagation details")
private AlarmPropagationInfo propagation;
private UserId userId;
public static AlarmUpdateRequest fromAlarm(Alarm a) {
return fromAlarm(a, null);
}
public static AlarmUpdateRequest fromAlarm(Alarm a, UserId userId) {
return AlarmUpdateRequest.builder()
.tenantId(a.getTenantId())
.alarmId(a.getId())
.severity((a.getSeverity()))
.startTs(a.getStartTs())
.endTs(a.getEndTs())
.details(a.getDetails())
.propagation(AlarmPropagationInfo.builder()
.propagate(a.isPropagate())
.propagateToOwner(a.isPropagateToOwner())
.propagateToTenant(a.isPropagateToTenant())
.propagateRelationTypes(a.getPropagateRelationTypes()).build())
.userId(userId)
.build();
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
@Data
@NoArgsConstructor
@ -35,6 +36,7 @@ public class EntityAlarm implements HasTenantId {
private String alarmType;
private CustomerId customerId;
private UserId assigneeId;
private AlarmId alarmId;
}

3
common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java

@ -23,6 +23,7 @@ import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasLabel;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
@ -37,7 +38,7 @@ import java.util.Optional;
@ApiModel
@EqualsAndHashCode(callSuper = true)
public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId, ExportableEntity<AssetId> {
public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasLabel, HasTenantId, HasCustomerId, ExportableEntity<AssetId> {
private static final long serialVersionUID = 2807343040519543363L;

2
common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java

@ -40,6 +40,8 @@ public enum ActionType {
ALARM_ACK(false),
ALARM_CLEAR(false),
ALARM_DELETE(false),
ALARM_ASSIGN(false),
ALARM_UNASSIGN(false),
LOGIN(false),
LOGOUT(false),
LOCKOUT(false),

3
common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java

@ -21,6 +21,7 @@ import lombok.EqualsAndHashCode;
import lombok.Setter;
import lombok.ToString;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasLabel;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
@ -35,7 +36,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
@ToString
@Setter
public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements HasName, HasTenantId, HasCustomerId {
public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements HasLabel, HasTenantId, HasCustomerId {
private static final long serialVersionUID = 4934987555236873728L;

2
common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java

@ -31,6 +31,8 @@ public enum EdgeEventActionType {
RPC_CALL,
ALARM_ACK,
ALARM_CLEAR,
ALARM_ASSIGN,
ALARM_UNASSIGN,
ASSIGNED_TO_EDGE,
UNASSIGNED_FROM_EDGE,
CREDENTIALS_REQUEST,

8
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -25,6 +25,14 @@ import java.util.UUID;
*/
public class EntityIdFactory {
public static EntityId getByTypeAndUuid(int type, String uuid) {
return getByTypeAndUuid(EntityType.values()[type], UUID.fromString(uuid));
}
public static EntityId getByTypeAndUuid(String type, String uuid) {
return getByTypeAndUuid(EntityType.valueOf(type), UUID.fromString(uuid));
}
public static EntityId getByTypeAndId(String type, String uuid) {
return getByTypeAndUuid(EntityType.valueOf(type), UUID.fromString(uuid));
}

27
common/data/src/main/java/org/thingsboard/server/common/data/id/NameLabelAndCustomerDetails.java

@ -0,0 +1,27 @@
/**
* 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.id;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class NameLabelAndCustomerDetails {
private final String name;
private final String label;
private final CustomerId customerId;
}

18
common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java

@ -15,24 +15,36 @@
*/
package org.thingsboard.server.common.data.query;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@EqualsAndHashCode(callSuper = true)
public class AlarmData extends AlarmInfo {
private static final long serialVersionUID = -7042457913823369638L;
@Getter
private final EntityId entityId;
@Getter
private final Map<EntityKeyType, Map<String, TsValue>> latest;
public AlarmData(Alarm alarm, String originatorName, EntityId entityId) {
super(alarm, originatorName);
public AlarmData(AlarmInfo main, AlarmData prototype) {
super(main);
this.entityId = prototype.entityId;
this.latest = new HashMap<>();
this.latest.putAll(prototype.getLatest());
}
public AlarmData(Alarm alarm, EntityId entityId) {
super(alarm);
this.entityId = entityId;
this.latest = new HashMap<>();
}

11
common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataPageLink.java

@ -19,11 +19,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.UserId;
import java.util.List;
@ -41,6 +40,7 @@ public class AlarmDataPageLink extends EntityDataPageLink {
private List<AlarmSearchStatus> statusList;
private List<AlarmSeverity> severityList;
private boolean searchPropagatedAlarms;
private UserId assigneeId;
public AlarmDataPageLink() {
super();
@ -49,7 +49,8 @@ public class AlarmDataPageLink extends EntityDataPageLink {
public AlarmDataPageLink(int pageSize, int page, String textSearch, EntityDataSortOrder sortOrder, boolean dynamic,
boolean searchPropagatedAlarms,
long startTs, long endTs, long timeWindow,
List<String> typeList, List<AlarmSearchStatus> statusList, List<AlarmSeverity> severityList) {
List<String> typeList, List<AlarmSearchStatus> statusList, List<AlarmSeverity> severityList,
UserId assigneeId) {
super(pageSize, page, textSearch, sortOrder, dynamic);
this.searchPropagatedAlarms = searchPropagatedAlarms;
this.startTs = startTs;
@ -58,6 +59,7 @@ public class AlarmDataPageLink extends EntityDataPageLink {
this.typeList = typeList;
this.statusList = statusList;
this.severityList = severityList;
this.assigneeId = assigneeId;
}
@JsonIgnore
@ -65,7 +67,8 @@ public class AlarmDataPageLink extends EntityDataPageLink {
return new AlarmDataPageLink(this.getPageSize(), this.getPage() + 1, this.getTextSearch(), this.getSortOrder(), this.isDynamic(),
this.searchPropagatedAlarms,
this.startTs, this.endTs, this.timeWindow,
this.typeList, this.statusList, this.severityList
this.typeList, this.statusList, this.severityList,
this.assigneeId
);
}
}

25
common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java

@ -15,10 +15,20 @@
*/
package org.thingsboard.server.common.data.security;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.UserCredentialsId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.Arrays;
import java.util.Objects;
import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo.getJson;
import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo.setJson;
@EqualsAndHashCode(callSuper = true)
public class UserCredentials extends BaseData<UserCredentialsId> {
@ -30,6 +40,20 @@ public class UserCredentials extends BaseData<UserCredentialsId> {
private String password;
private String activateToken;
private String resetToken;
@NoXss
private transient JsonNode additionalInfo;
@JsonIgnore
private byte[] additionalInfoBytes;
public JsonNode getAdditionalInfo() {
return getJson(() -> additionalInfo, () -> additionalInfoBytes);
}
public void setAdditionalInfo(JsonNode settings) {
setJson(settings, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes);
}
public UserCredentials() {
super();
@ -46,6 +70,7 @@ public class UserCredentials extends BaseData<UserCredentialsId> {
this.enabled = userCredentials.isEnabled();
this.activateToken = userCredentials.getActivateToken();
this.resetToken = userCredentials.getResetToken();
setAdditionalInfo(userCredentials.getAdditionalInfo());
}
public UserId getUserId() {

3
common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.widget;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
@ -25,7 +26,7 @@ import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasTenantId {
public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasName, HasTenantId {
private static final long serialVersionUID = 8388684344603660756L;

3
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java

@ -25,6 +25,7 @@ import lombok.Setter;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.HasTitle;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
@ -33,7 +34,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel
@EqualsAndHashCode(callSuper = true)
public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements HasName, HasTenantId, ExportableEntity<WidgetsBundleId> {
public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements HasName, HasTenantId, ExportableEntity<WidgetsBundleId>, HasTitle {
private static final long serialVersionUID = -7627368878362410489L;

25
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java

@ -15,17 +15,21 @@
*/
package org.thingsboard.server.dao.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.AlarmData;
@ -44,12 +48,16 @@ public interface AlarmDao extends Dao<Alarm> {
Alarm findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
ListenableFuture<Alarm> findLatestByOriginatorAndTypeAsync(TenantId tenantId, EntityId originator, String type);
Alarm findAlarmById(TenantId tenantId, UUID key);
ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, UUID key);
AlarmInfo findAlarmInfoById(TenantId tenantId, UUID key);
Alarm save(TenantId tenantId, Alarm alarm);
PageData<AlarmInfo> findAlarms(TenantId tenantId, AlarmQuery query);
@ -58,7 +66,7 @@ public interface AlarmDao extends Dao<Alarm> {
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> status);
Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, AlarmStatusFilter asf, String assigneeId);
PageData<AlarmId> findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink);
@ -67,4 +75,17 @@ public interface AlarmDao extends Dao<Alarm> {
List<EntityAlarm> findEntityAlarmRecords(TenantId tenantId, AlarmId id);
void deleteEntityAlarmRecords(TenantId tenantId, EntityId entityId);
AlarmApiCallResult createOrUpdateActiveAlarm(AlarmCreateOrUpdateActiveRequest request, boolean alarmCreationEnabled);
AlarmApiCallResult updateAlarm(AlarmUpdateRequest request);
AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId id, long ackTs);
AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details);
AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTime);
AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long unassignTime);
}

261
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -20,20 +20,22 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmModificationRequest;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException;
import org.thingsboard.server.common.data.id.AlarmId;
@ -41,6 +43,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
@ -50,11 +53,11 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantService;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -63,8 +66,6 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -73,32 +74,52 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Service("AlarmDaoService")
@Slf4j
@RequiredArgsConstructor
public class BaseAlarmService extends AbstractEntityService implements AlarmService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
@Autowired
private AlarmDao alarmDao;
private final TenantService tenantService;
private final AlarmDao alarmDao;
private final EntityService entityService;
private final DataValidator<Alarm> alarmDataValidator;
@Autowired
private EntityService entityService;
@Override
public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
validateAlarmRequest(request);
return withPropagated(alarmDao.updateAlarm(request));
}
@Autowired
private DataValidator<Alarm> alarmDataValidator;
@Override
public AlarmApiCallResult createAlarm(AlarmCreateOrUpdateActiveRequest request) {
return createAlarm(request, true);
}
protected ExecutorService readResultsProcessingExecutor;
@Override
public AlarmApiCallResult createAlarm(AlarmCreateOrUpdateActiveRequest request, boolean alarmCreationEnabled) {
validateAlarmRequest(request);
CustomerId customerId = entityService.fetchEntityCustomerId(request.getTenantId(), request.getOriginator()).orElse(null);
if (customerId == null && request.getCustomerId() != null) {
throw new DataValidationException("Can't assign alarm to customer. Originator is not assigned to customer!");
} else if (customerId != null && request.getCustomerId() != null && !customerId.equals(request.getCustomerId())) {
throw new DataValidationException("Can't assign alarm to customer. Originator belongs to different customer!");
}
request.setCustomerId(customerId);
AlarmApiCallResult result = alarmDao.createOrUpdateActiveAlarm(request, alarmCreationEnabled);
if (!result.isSuccessful() && !alarmCreationEnabled) {
throw new ApiUsageLimitsExceededException("Alarms creation is disabled");
}
return withPropagated(result);
}
@PostConstruct
public void startExecutor() {
readResultsProcessingExecutor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName("alarm-service"));
@Override
public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) {
return withPropagated(alarmDao.acknowledgeAlarm(tenantId, alarmId, ackTs));
}
@PreDestroy
public void stopExecutor() {
if (readResultsProcessingExecutor != null) {
readResultsProcessingExecutor.shutdownNow();
}
@Override
public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details) {
return withPropagated(alarmDao.clearAlarm(tenantId, alarmId, clearTs, details));
}
@Override
@ -116,8 +137,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
if (alarm.getEndTs() == 0L) {
alarm.setEndTs(alarm.getStartTs());
}
alarm.setCustomerId(entityService.fetchEntityCustomerId(alarm.getTenantId(), alarm.getOriginator()).get());
alarm.setCustomerId(entityService.fetchEntityCustomerId(alarm.getTenantId(), alarm.getOriginator()).orElse(null));
if (alarm.getId() == null) {
// Atomic update and return alarm + assignee.
Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType());
if (existing == null || existing.getStatus().isCleared()) {
if (!alarmCreationEnabled) {
@ -135,6 +157,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
}
@Override
public Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
return alarmDao.findLatestActiveByOriginatorAndType(tenantId, originator, type);
}
@Override
public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
return alarmDao.findLatestByOriginatorAndTypeAsync(tenantId, originator, type);
}
@ -147,6 +175,20 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return alarmDao.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds);
}
@Override
@Transactional
public AlarmApiCallResult delAlarm(TenantId tenantId, AlarmId alarmId) {
log.debug("Deleting Alarm Id: {}", alarmId);
AlarmInfo alarm = alarmDao.findAlarmInfoById(tenantId, alarmId.getId());
if (alarm == null) {
return AlarmApiCallResult.builder().successful(false).build();
} else {
deleteEntityRelations(tenantId, alarm.getId());
alarmDao.removeById(tenantId, alarm.getUuidId());
return AlarmApiCallResult.builder().alarm(alarm).successful(true).build();
}
}
@Override
@Transactional
public AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId) {
@ -165,10 +207,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
log.debug("New Alarm : {}", alarm);
Alarm saved = alarmDao.save(alarm.getTenantId(), alarm);
List<EntityId> propagatedEntitiesList = createEntityAlarmRecords(saved);
return new AlarmOperationResult(saved, true, true, null, propagatedEntitiesList);
return new AlarmOperationResult(saved, true, true, propagatedEntitiesList);
}
private List<EntityId> createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException {
private List<EntityId> createEntityAlarmRecords(Alarm alarm) throws ExecutionException, InterruptedException {
Set<EntityId> propagatedEntitiesSet = new LinkedHashSet<>();
propagatedEntitiesSet.add(alarm.getOriginator());
if (alarm.isPropagate()) {
@ -226,45 +268,41 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
@Override
public ListenableFuture<AlarmOperationResult> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTime) {
return getAndUpdateAsync(tenantId, alarmId, new Function<Alarm, AlarmOperationResult>() {
@Nullable
@Override
public AlarmOperationResult apply(@Nullable Alarm alarm) {
if (alarm == null || alarm.getStatus().isAck()) {
return new AlarmOperationResult(alarm, false);
} else {
AlarmStatus oldStatus = alarm.getStatus();
AlarmStatus newStatus = oldStatus.isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK;
alarm.setStatus(newStatus);
alarm.setAckTs(ackTime);
alarm = alarmDao.save(alarm.getTenantId(), alarm);
return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)));
}
}
});
Alarm alarm = alarmDao.findAlarmById(tenantId, alarmId.getId());
if (alarm == null || alarm.getStatus().isAck()) {
return Futures.immediateFuture(new AlarmOperationResult(alarm, false));
} else {
alarm.setAcknowledged(true);
alarm.setAckTs(ackTime);
alarm = alarmDao.save(alarm.getTenantId(), alarm);
return Futures.immediateFuture(new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))));
}
}
@Override
public ListenableFuture<AlarmOperationResult> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTime) {
return getAndUpdateAsync(tenantId, alarmId, new Function<Alarm, AlarmOperationResult>() {
@Nullable
@Override
public AlarmOperationResult apply(@Nullable Alarm alarm) {
if (alarm == null || alarm.getStatus().isCleared()) {
return new AlarmOperationResult(alarm, false);
} else {
AlarmStatus oldStatus = alarm.getStatus();
AlarmStatus newStatus = oldStatus.isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK;
alarm.setStatus(newStatus);
alarm.setClearTs(clearTime);
if (details != null) {
alarm.setDetails(details);
}
alarm = alarmDao.save(alarm.getTenantId(), alarm);
return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)));
}
Alarm alarm = alarmDao.findAlarmById(tenantId, alarmId.getId());
if (alarm == null || alarm.getStatus().isCleared()) {
return Futures.immediateFuture(new AlarmOperationResult(alarm, false));
} else {
alarm.setCleared(true);
alarm.setClearTs(clearTime);
if (details != null) {
alarm.setDetails(details);
}
});
alarm = alarmDao.save(alarm.getTenantId(), alarm);
return Futures.immediateFuture(new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))));
}
}
@Override
public AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTime) {
return withPropagated(alarmDao.assignAlarm(tenantId, alarmId, assigneeId, assignTime));
}
@Override
public AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long unassignTime) {
return withPropagated(alarmDao.unassignAlarm(tenantId, alarmId, unassignTime));
}
@Override
@ -282,60 +320,35 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
@Override
public ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) {
public AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId) {
log.trace("Executing findAlarmInfoByIdAsync [{}]", alarmId);
validateId(alarmId, "Incorrect alarmId " + alarmId);
return Futures.transform(alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()),
a -> {
AlarmInfo alarmInfo = new AlarmInfo(a);
alarmInfo.setOriginatorName(
entityService.fetchEntityName(tenantId, alarmInfo.getOriginator()).orElse("N/A"));
return alarmInfo;
}, MoreExecutors.directExecutor());
return alarmDao.findAlarmInfoById(tenantId, alarmId.getId());
}
@Override
public ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) {
PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query);
if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) {
return fetchAlarmsOriginators(tenantId, alarms);
}
return Futures.immediateFuture(alarms);
return Futures.immediateFuture(alarmDao.findAlarms(tenantId, query));
}
@Override
public ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) {
PageData<AlarmInfo> alarms = alarmDao.findCustomerAlarms(tenantId, customerId, query);
if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) {
return fetchAlarmsOriginators(tenantId, alarms);
}
return Futures.immediateFuture(alarms);
}
private ListenableFuture<PageData<AlarmInfo>> fetchAlarmsOriginators(TenantId tenantId, PageData<AlarmInfo> alarms) {
List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(alarms.getData().size());
for (AlarmInfo alarmInfo : alarms.getData()) {
alarmInfo.setOriginatorName(
entityService.fetchEntityName(tenantId, alarmInfo.getOriginator()).orElse("Deleted"));
alarmFutures.add(Futures.immediateFuture(alarmInfo));
}
return Futures.transform(Futures.successfulAsList(alarmFutures),
alarmInfos -> new PageData<>(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(),
alarms.hasNext()), MoreExecutors.directExecutor());
return Futures.immediateFuture(alarmDao.findCustomerAlarms(tenantId, customerId, query));
}
@Override
public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus,
AlarmStatus alarmStatus) {
Set<AlarmStatus> statusList = null;
AlarmStatus alarmStatus, String assigneeId) {
AlarmStatusFilter asf;
if (alarmSearchStatus != null) {
statusList = alarmSearchStatus.getStatuses();
asf = AlarmStatusFilter.from(alarmSearchStatus);
} else if (alarmStatus != null) {
statusList = Collections.singleton(alarmStatus);
asf = AlarmStatusFilter.from(alarmStatus);
} else {
asf = AlarmStatusFilter.empty();
}
Set<AlarmSeverity> alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList);
Set<AlarmSeverity> alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, asf, assigneeId);
return alarmSeverities.stream().min(AlarmSeverity::compareTo).orElse(null);
}
@ -357,10 +370,15 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
if (alarm.getAckTs() > existing.getAckTs()) {
existing.setAckTs(alarm.getAckTs());
}
existing.setStatus(alarm.getStatus());
if (alarm.getAssignTs() > existing.getAssignTs()) {
existing.setAssignTs(alarm.getAssignTs());
}
existing.setAcknowledged(alarm.isAcknowledged());
existing.setCleared(alarm.isCleared());
existing.setSeverity(alarm.getSeverity());
existing.setDetails(alarm.getDetails());
existing.setCustomerId(alarm.getCustomerId());
existing.setAssigneeId(alarm.getAssigneeId());
existing.setPropagate(existing.isPropagate() || alarm.isPropagate());
existing.setPropagateToOwner(existing.isPropagateToOwner() || alarm.isPropagateToOwner());
existing.setPropagateToTenant(existing.isPropagateToTenant() || alarm.isPropagateToTenant());
@ -378,6 +396,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return existing;
}
private List<EntityId> getPropagationEntityIdsList(Alarm alarm) {
return new ArrayList<>(getPropagationEntityIds(alarm));
}
private Set<EntityId> getPropagationEntityIds(Alarm alarm) {
if (alarm.isPropagate() || alarm.isPropagateToOwner() || alarm.isPropagateToTenant()) {
List<EntityAlarm> entityAlarms = alarmDao.findEntityAlarmRecords(alarm.getTenantId(), alarm.getId());
@ -388,7 +410,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
private void createEntityAlarmRecord(TenantId tenantId, EntityId entityId, Alarm alarm) {
EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), alarm.getId());
EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), null, alarm.getId());
try {
alarmDao.createEntityAlarmRecord(entityAlarm);
} catch (Exception e) {
@ -396,12 +418,6 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
}
private <T> ListenableFuture<T> getAndUpdateAsync(TenantId tenantId, AlarmId alarmId, Function<Alarm, T> function) {
validateId(alarmId, "Alarm id should be specified!");
ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId());
return Futures.transform(entity, function, readResultsProcessingExecutor);
}
private <T> T getAndUpdate(TenantId tenantId, AlarmId alarmId, Function<Alarm, T> function) {
validateId(alarmId, "Alarm id should be specified!");
Alarm entity = alarmDao.findAlarmById(tenantId, alarmId.getId());
@ -418,4 +434,39 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return EntityType.ALARM;
}
//TODO: refactor to use efficient caching.
private AlarmApiCallResult withPropagated(AlarmApiCallResult result) {
if (result.isSuccessful() && result.getAlarm() != null) {
List<EntityId> propagationEntities;
if (result.isPropagationChanged()) {
try {
propagationEntities = createEntityAlarmRecords(result.getAlarm());
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
} else {
propagationEntities = getPropagationEntityIdsList(result.getAlarm());
}
return new AlarmApiCallResult(result, propagationEntities);
} else {
return result;
}
}
private void validateAlarmRequest(AlarmModificationRequest request) {
ConstraintValidator.validateFields(request);
if (request.getEndTs() > 0 && request.getStartTs() > request.getEndTs()) {
throw new DataValidationException("Alarm start ts can't be greater then alarm end ts!");
}
if (!tenantService.tenantExists(request.getTenantId())) {
throw new DataValidationException("Alarm is referencing to non-existent tenant!");
}
if (request.getStartTs() == 0L) {
request.setStartTs(System.currentTimeMillis());
}
if (request.getEndTs() == 0L) {
request.setEndTs(request.getStartTs());
}
}
}

2
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java

@ -166,6 +166,8 @@ public class AuditLogServiceImpl implements AuditLogService {
case UPDATED:
case ALARM_ACK:
case ALARM_CLEAR:
case ALARM_ASSIGN:
case ALARM_UNASSIGN:
case RELATIONS_DELETED:
case ASSIGNED_TO_TENANT:
if (entity != null) {

82
dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

@ -20,7 +20,12 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasEmail;
import org.thingsboard.server.common.data.HasLabel;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTitle;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.NameLabelAndCustomerDetails;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
@ -34,6 +39,7 @@ import org.thingsboard.server.common.data.query.RelationsQueryFilter;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import java.util.Optional;
import java.util.function.Function;
import static org.thingsboard.server.common.data.id.EntityId.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateEntityDataPageLink;
@ -77,35 +83,67 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
@Override
public Optional<String> fetchEntityName(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchEntityName [{}]", entityId);
EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType());
Optional<HasId<?>> hasIdOpt = entityDaoService.findEntity(tenantId, entityId);
if (hasIdOpt.isPresent()) {
HasId<?> hasId = hasIdOpt.get();
if (hasId instanceof HasName) {
HasName hasName = (HasName) hasId;
return Optional.ofNullable(hasName.getName());
}
}
return Optional.empty();
return fetchAndConvert(tenantId, entityId, this::getName);
}
@Override
public Optional<String> fetchEntityLabel(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchEntityLabel [{}]", entityId);
return fetchAndConvert(tenantId, entityId, this::getLabel);
}
@Override
public Optional<CustomerId> fetchEntityCustomerId(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchEntityCustomerId [{}]", entityId);
return fetchAndConvert(tenantId, entityId, this::getCustomerId);
}
@Override
public Optional<NameLabelAndCustomerDetails> fetchNameLabelAndCustomerDetails(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchNameLabelAndCustomerDetails [{}]", entityId);
return fetchAndConvert(tenantId, entityId, this::getNameLabelAndCustomerDetails);
}
private <T> Optional<T> fetchAndConvert(TenantId tenantId, EntityId entityId, Function<HasId<?>, T> converter) {
EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType());
Optional<HasId<?>> hasIdOpt = entityDaoService.findEntity(tenantId, entityId);
if (hasIdOpt.isPresent()) {
HasId<?> hasId = hasIdOpt.get();
if (hasId instanceof HasCustomerId) {
HasCustomerId hasCustomerId = (HasCustomerId) hasId;
CustomerId customerId = hasCustomerId.getCustomerId();
if (customerId == null) {
customerId = NULL_CUSTOMER_ID;
}
return Optional.of(customerId);
Optional<HasId<?>> entityOpt = entityDaoService.findEntity(tenantId, entityId);
return entityOpt.map(converter);
}
private String getName(HasId<?> entity) {
return entity instanceof HasName ? ((HasName) entity).getName() : null;
}
private String getLabel(HasId<?> entity) {
if (entity instanceof HasTitle && StringUtils.isNotEmpty(((HasTitle) entity).getTitle())) {
return ((HasTitle) entity).getTitle();
}
if (entity instanceof HasLabel && StringUtils.isNotEmpty(((HasLabel) entity).getLabel())) {
return ((HasLabel) entity).getLabel();
}
if (entity instanceof HasEmail && StringUtils.isNotEmpty(((HasEmail) entity).getEmail())) {
return ((HasEmail) entity).getEmail();
}
if (entity instanceof HasName && StringUtils.isNotEmpty(((HasName) entity).getName())) {
return ((HasName) entity).getName();
}
return null;
}
private CustomerId getCustomerId(HasId<?> entity) {
if (entity instanceof HasCustomerId) {
HasCustomerId hasCustomerId = (HasCustomerId) entity;
CustomerId customerId = hasCustomerId.getCustomerId();
if (customerId == null) {
customerId = NULL_CUSTOMER_ID;
}
return customerId;
}
return Optional.of(NULL_CUSTOMER_ID);
return NULL_CUSTOMER_ID;
}
private NameLabelAndCustomerDetails getNameLabelAndCustomerDetails(HasId<?> entity) {
return new NameLabelAndCustomerDetails(getName(entity), getLabel(entity), getCustomerId(entity));
}
private static void validateEntityCountQuery(EntityCountQuery query) {
@ -126,7 +164,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
}
private static void validateRelationQuery(RelationsQueryFilter queryFilter) {
if (queryFilter.isMultiRoot() && queryFilter.getMultiRootEntitiesType() ==null){
if (queryFilter.isMultiRoot() && queryFilter.getMultiRootEntitiesType() == null) {
throw new IncorrectParameterException("Multi-root relation query filter should contain 'multiRootEntitiesType'");
}
if (queryFilter.isMultiRoot() && CollectionUtils.isEmpty(queryFilter.getMultiRootEntityIds())) {

17
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -41,6 +41,7 @@ public class ModelConstants {
public static final String USER_ID_PROPERTY = "user_id";
public static final String TENANT_ID_PROPERTY = "tenant_id";
public static final String CUSTOMER_ID_PROPERTY = "customer_id";
public static final String ASSIGNEE_ID_PROPERTY = "assignee_id";
public static final String DEVICE_ID_PROPERTY = "device_id";
public static final String TITLE_PROPERTY = "title";
public static final String NAME_PROPERTY = "name";
@ -82,6 +83,7 @@ public class ModelConstants {
public static final String USER_CREDENTIALS_PASSWORD_PROPERTY = "password"; //NOSONAR, the constant used to identify password column name (not password value itself)
public static final String USER_CREDENTIALS_ACTIVATE_TOKEN_PROPERTY = "activate_token";
public static final String USER_CREDENTIALS_RESET_TOKEN_PROPERTY = "reset_token";
public static final String USER_CREDENTIALS_ADDITIONAL_PROPERTY = "additional_info";
public static final String USER_CREDENTIALS_BY_USER_COLUMN_FAMILY_NAME = "user_credentials_by_user";
public static final String USER_CREDENTIALS_BY_ACTIVATE_TOKEN_COLUMN_FAMILY_NAME = "user_credentials_by_activate_token";
@ -287,24 +289,35 @@ public class ModelConstants {
*/
public static final String ENTITY_ALARM_COLUMN_FAMILY_NAME = "entity_alarm";
public static final String ALARM_COLUMN_FAMILY_NAME = "alarm";
public static final String ALARM_VIEW_NAME = "alarm_info";
public static final String ALARM_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ALARM_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String ALARM_TYPE_PROPERTY = "type";
public static final String ALARM_DETAILS_PROPERTY = "details";
public static final String ALARM_DETAILS_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String ALARM_STATUS_PROPERTY = "status";
public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id";
public static final String ALARM_ORIGINATOR_NAME_PROPERTY = "originator_name";
public static final String ALARM_ORIGINATOR_LABEL_PROPERTY = "originator_label";
public static final String ALARM_ORIGINATOR_TYPE_PROPERTY = "originator_type";
public static final String ALARM_SEVERITY_PROPERTY = "severity";
public static final String ALARM_STATUS_PROPERTY = "status";
public static final String ALARM_ASSIGNEE_ID_PROPERTY = "assignee_id";
public static final String ALARM_ASSIGNEE_FIRST_NAME_PROPERTY = "assignee_first_name";
public static final String ALARM_ASSIGNEE_LAST_NAME_PROPERTY = "assignee_last_name";
public static final String ALARM_ASSIGNEE_EMAIL_PROPERTY = "assignee_email";
public static final String ALARM_START_TS_PROPERTY = "start_ts";
public static final String ALARM_END_TS_PROPERTY = "end_ts";
public static final String ALARM_ACKNOWLEDGED_PROPERTY = "acknowledged";
public static final String ALARM_ACK_TS_PROPERTY = "ack_ts";
public static final String ALARM_CLEARED_PROPERTY = "cleared";
public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts";
public static final String ALARM_ASSIGN_TS_PROPERTY = "assign_ts";
public static final String ALARM_PROPAGATE_PROPERTY = "propagate";
public static final String ALARM_PROPAGATE_TO_OWNER_PROPERTY = "propagate_to_owner";
public static final String ALARM_PROPAGATE_TO_TENANT_PROPERTY = "propagate_to_tenant";
public static final String ALARM_PROPAGATE_RELATION_TYPES = "propagate_relation_types";
public static final String ALARM_OPERATION_RESULT_PROPERTY = "operation_result";
public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id";
public static final String ALARM_COMMENT_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;

45
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java

@ -25,11 +25,11 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.model.BaseEntity;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
@ -43,7 +43,11 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACKNOWLEDGED_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGN_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEARED_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CUSTOMER_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY;
@ -55,7 +59,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_TO
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_TO_TENANT_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TENANT_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERTY;
@ -84,9 +87,9 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
@Column(name = ALARM_SEVERITY_PROPERTY)
private AlarmSeverity severity;
@Enumerated(EnumType.STRING)
@Column(name = ALARM_STATUS_PROPERTY)
private AlarmStatus status;
@Type(type="pg-uuid")
@Column(name = ALARM_ASSIGNEE_ID_PROPERTY)
private UUID assigneeId;
@Column(name = ALARM_START_TS_PROPERTY)
private Long startTs;
@ -94,14 +97,23 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
@Column(name = ALARM_END_TS_PROPERTY)
private Long endTs;
@Column(name = ALARM_ACKNOWLEDGED_PROPERTY)
private boolean acknowledged;
@Column(name = ALARM_ACK_TS_PROPERTY)
private Long ackTs;
@Column(name = ALARM_CLEARED_PROPERTY)
private boolean cleared;
@Column(name = ALARM_CLEAR_TS_PROPERTY)
private Long clearTs;
@Column(name = ALARM_ASSIGN_TS_PROPERTY)
private Long assignTs;
@Type(type = "json")
@Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY)
@Column(name = ModelConstants.ALARM_DETAILS_PROPERTY)
private JsonNode details;
@Column(name = ALARM_PROPAGATE_PROPERTY)
@ -136,7 +148,11 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.originatorType = alarm.getOriginator().getEntityType();
this.type = alarm.getType();
this.severity = alarm.getSeverity();
this.status = alarm.getStatus();
this.acknowledged = alarm.isAcknowledged();
this.cleared = alarm.isCleared();
if (alarm.getAssigneeId() != null) {
this.assigneeId = alarm.getAssigneeId().getId();
}
this.propagate = alarm.isPropagate();
this.propagateToOwner = alarm.isPropagateToOwner();
this.propagateToTenant = alarm.isPropagateToTenant();
@ -144,11 +160,12 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.endTs = alarm.getEndTs();
this.ackTs = alarm.getAckTs();
this.clearTs = alarm.getClearTs();
this.assignTs = alarm.getAssignTs();
this.details = alarm.getDetails();
if (!CollectionUtils.isEmpty(alarm.getPropagateRelationTypes())) {
this.propagateRelationTypes = String.join(",", alarm.getPropagateRelationTypes());
} else {
this.propagateRelationTypes = null;
this.propagateRelationTypes = "";
}
}
@ -162,7 +179,9 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.originatorType = alarmEntity.getOriginatorType();
this.type = alarmEntity.getType();
this.severity = alarmEntity.getSeverity();
this.status = alarmEntity.getStatus();
this.acknowledged = alarmEntity.isAcknowledged();
this.cleared = alarmEntity.isCleared();
this.assigneeId = alarmEntity.getAssigneeId();
this.propagate = alarmEntity.getPropagate();
this.propagateToOwner = alarmEntity.getPropagateToOwner();
this.propagateToTenant = alarmEntity.getPropagateToTenant();
@ -170,6 +189,7 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.endTs = alarmEntity.getEndTs();
this.ackTs = alarmEntity.getAckTs();
this.clearTs = alarmEntity.getClearTs();
this.assignTs = alarmEntity.getAssignTs();
this.details = alarmEntity.getDetails();
this.propagateRelationTypes = alarmEntity.getPropagateRelationTypes();
}
@ -186,7 +206,11 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId));
alarm.setType(type);
alarm.setSeverity(severity);
alarm.setStatus(status);
alarm.setAcknowledged(acknowledged);
alarm.setCleared(cleared);
if (assigneeId != null) {
alarm.setAssigneeId(new UserId(assigneeId));
}
alarm.setPropagate(propagate);
alarm.setPropagateToOwner(propagateToOwner);
alarm.setPropagateToTenant(propagateToTenant);
@ -194,6 +218,7 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
alarm.setEndTs(endTs);
alarm.setAckTs(ackTs);
alarm.setClearTs(clearTs);
alarm.setAssignTs(assignTs);
alarm.setDetails(details);
if (!StringUtils.isEmpty(propagateRelationTypes)) {
alarm.setPropagateRelationTypes(Arrays.asList(propagateRelationTypes.split(",")));

49
dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java

@ -17,24 +17,63 @@ package org.thingsboard.server.dao.model.sql;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity;
import org.thingsboard.server.dao.sqlts.latest.SearchTsKvLatestRepository;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.NamedStoredProcedureQuery;
import javax.persistence.ParameterMode;
import javax.persistence.StoredProcedureParameter;
import javax.persistence.Table;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_VIEW_NAME;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = ALARM_VIEW_NAME)
public class AlarmInfoEntity extends AbstractAlarmEntity<AlarmInfo> {
@Column(name = ALARM_ORIGINATOR_NAME_PROPERTY)
private String originatorName;
@Column(name = ALARM_ORIGINATOR_LABEL_PROPERTY)
private String originatorLabel;
@Column(name = ALARM_ASSIGNEE_FIRST_NAME_PROPERTY)
private String assigneeFirstName;
@Column(name = ALARM_ASSIGNEE_LAST_NAME_PROPERTY)
private String assigneeLastName;
@Column(name = ALARM_ASSIGNEE_EMAIL_PROPERTY)
private String assigneeEmail;
@Column(name = ALARM_STATUS_PROPERTY)
private String status;
public AlarmInfoEntity() {
super();
}
public AlarmInfoEntity(AlarmEntity alarmEntity) {
super(alarmEntity);
}
@Override
public AlarmInfo toData() {
return new AlarmInfo(super.toAlarm(), this.originatorName);
AlarmInfo alarmInfo = new AlarmInfo(super.toAlarm());
alarmInfo.setOriginatorName(originatorName);
alarmInfo.setOriginatorLabel(originatorLabel);
if (getAssigneeId() != null) {
alarmInfo.setAssignee(new AlarmAssignee(new UserId(getAssigneeId()), assigneeFirstName, assigneeLastName, assigneeEmail));
}
return alarmInfo;
}
}

2
dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.model.ToData;
import javax.persistence.Column;
@ -30,6 +31,7 @@ import javax.persistence.IdClass;
import javax.persistence.Table;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ASSIGNEE_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.CREATED_TIME_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ALARM_COLUMN_FAMILY_NAME;

8
dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java

@ -15,8 +15,10 @@
*/
package org.thingsboard.server.dao.model.sql;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.thingsboard.server.common.data.id.UserCredentialsId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials;
@ -50,6 +52,10 @@ public final class UserCredentialsEntity extends BaseSqlEntity<UserCredentials>
@Column(name = ModelConstants.USER_CREDENTIALS_RESET_TOKEN_PROPERTY, unique = true)
private String resetToken;
@Type(type = "json")
@Column(name = ModelConstants.USER_CREDENTIALS_ADDITIONAL_PROPERTY)
private JsonNode additionalInfo;
public UserCredentialsEntity() {
super();
}
@ -66,6 +72,7 @@ public final class UserCredentialsEntity extends BaseSqlEntity<UserCredentials>
this.password = userCredentials.getPassword();
this.activateToken = userCredentials.getActivateToken();
this.resetToken = userCredentials.getResetToken();
this.additionalInfo = userCredentials.getAdditionalInfo();
}
@Override
@ -79,6 +86,7 @@ public final class UserCredentialsEntity extends BaseSqlEntity<UserCredentials>
userCredentials.setPassword(password);
userCredentials.setActivateToken(activateToken);
userCredentials.setResetToken(resetToken);
userCredentials.setAdditionalInfo(additionalInfo);
return userCredentials;
}

105
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java

@ -18,10 +18,11 @@ package org.thingsboard.server.dao.sql.alarm;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.dao.model.sql.AlarmEntity;
import org.thingsboard.server.dao.model.sql.AlarmInfoEntity;
@ -39,7 +40,13 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Param("alarmType") String alarmType,
Pageable pageable);
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " +
@Query("SELECT a FROM AlarmEntity a WHERE a.originatorId = :originatorId AND a.type = :alarmType AND a.cleared = FALSE ORDER BY a.createdTime DESC")
List<AlarmEntity> findLatestActiveByOriginatorAndType(@Param("originatorId") UUID originatorId,
@Param("alarmType") String alarmType,
Pageable pageable);
@Query(value = "SELECT a " +
"FROM AlarmInfoEntity a " +
"LEFT JOIN EntityAlarmEntity ea ON a.id = ea.alarmId " +
"WHERE a.tenantId = :tenantId " +
"AND ea.tenantId = :tenantId " +
@ -47,14 +54,16 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"AND ea.entityType = :affectedEntityType " +
"AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " +
"AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) "
,
countQuery = "" +
"SELECT count(a) " + //alarms with relations only
"FROM AlarmEntity a " +
"FROM AlarmInfoEntity a " +
"LEFT JOIN EntityAlarmEntity ea ON a.id = ea.alarmId " +
"WHERE a.tenantId = :tenantId " +
"AND ea.tenantId = :tenantId " +
@ -62,7 +71,9 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"AND ea.entityType = :affectedEntityType " +
"AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " +
"AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ")
@ -71,51 +82,69 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Param("affectedEntityType") String affectedEntityType,
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
@Param("alarmStatuses") Set<AlarmStatus> alarmStatuses,
@Param("clearFilterEnabled") boolean clearFilterEnabled,
@Param("clearFilter") boolean clearFilter,
@Param("ackFilterEnabled") boolean ackFilterEnabled,
@Param("ackFilter") boolean ackFilter,
@Param("assigneeId") String assigneeId,
@Param("searchText") String searchText,
Pageable pageable);
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " +
@Query(value = "SELECT a " +
"FROM AlarmInfoEntity a " +
"WHERE a.tenantId = :tenantId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ",
countQuery = "" +
"SELECT count(a) " +
"FROM AlarmEntity a " +
"FROM AlarmInfoEntity a " +
"WHERE a.tenantId = :tenantId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ")
Page<AlarmInfoEntity> findAllAlarms(@Param("tenantId") UUID tenantId,
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
@Param("alarmStatuses") Set<AlarmStatus> alarmStatuses,
@Param("clearFilterEnabled") boolean clearFilterEnabled,
@Param("clearFilter") boolean clearFilter,
@Param("ackFilterEnabled") boolean ackFilterEnabled,
@Param("ackFilter") boolean ackFilter,
@Param("assigneeId") String assigneeId,
@Param("searchText") String searchText,
Pageable pageable);
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " +
@Query(value = "SELECT a " +
"FROM AlarmInfoEntity a " +
"WHERE a.tenantId = :tenantId AND a.customerId = :customerId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) "
,
countQuery = "" +
"SELECT count(a) " +
"FROM AlarmEntity a " +
"FROM AlarmInfoEntity a " +
"WHERE a.tenantId = :tenantId AND a.customerId = :customerId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ")
@ -123,7 +152,11 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Param("customerId") UUID customerId,
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
@Param("alarmStatuses") Set<AlarmStatus> alarmStatuses,
@Param("clearFilterEnabled") boolean clearFilterEnabled,
@Param("clearFilter") boolean clearFilter,
@Param("ackFilterEnabled") boolean ackFilterEnabled,
@Param("ackFilter") boolean ackFilter,
@Param("assigneeId") String assigneeId,
@Param("searchText") String searchText,
Pageable pageable);
@ -133,13 +166,49 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"AND ea.tenantId = :tenantId " +
"AND ea.entityId = :affectedEntityId " +
"AND ea.entityType = :affectedEntityType " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses))")
"AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId))")
Set<AlarmSeverity> findAlarmSeverities(@Param("tenantId") UUID tenantId,
@Param("affectedEntityId") UUID affectedEntityId,
@Param("affectedEntityType") String affectedEntityType,
@Param("alarmStatuses") Set<AlarmStatus> alarmStatuses);
@Param("clearFilterEnabled") boolean clearFilterEnabled,
@Param("clearFilter") boolean clearFilter,
@Param("ackFilterEnabled") boolean ackFilterEnabled,
@Param("ackFilter") boolean ackFilter,
@Param("assigneeId") String assigneeId);
@Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.createdTime < :time AND a.endTs < :time")
Page<UUID> findAlarmsIdsByEndTsBeforeAndTenantId(@Param("time") Long time, @Param("tenantId") UUID tenantId, Pageable pageable);
@Query(value = "SELECT a FROM AlarmInfoEntity a WHERE a.tenantId = :tenantId AND a.id = :alarmId")
AlarmInfoEntity findAlarmInfoById(@Param("tenantId") UUID tenantId, @Param("alarmId") UUID alarmId);
@Procedure(procedureName = "create_or_update_active_alarm")
String createOrUpdateActiveAlarm(@Param("t_id") UUID tenantId, @Param("c_id") UUID customerId,
@Param("a_id") UUID alarmId, @Param("a_created_ts") long createdTime,
@Param("a_o_id") UUID originatorId, @Param("a_o_type") int originatorType,
@Param("a_type") String type, @Param("a_severity") String severity,
@Param("a_start_ts") long startTs, @Param("a_end_ts") long endTs, @Param("a_details") String detailsAsString,
@Param("a_propagate") boolean propagate, @Param("a_propagate_to_owner") boolean propagateToOwner,
@Param("a_propagate_to_tenant") boolean propagateToTenant, @Param("a_propagation_types") String propagationTypes,
@Param("a_creation_enabled") boolean alarmCreationEnabled);
@Procedure(procedureName = "update_alarm")
String updateAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_severity") String severity,
@Param("a_start_ts") long startTs, @Param("a_end_ts") long endTs, @Param("a_details") String detailsAsString,
@Param("a_propagate") boolean propagate, @Param("a_propagate_to_owner") boolean propagateToOwner,
@Param("a_propagate_to_tenant") boolean propagateToTenant, @Param("a_propagation_types") String propagationTypes);
@Procedure(procedureName = "acknowledge_alarm")
String acknowledgeAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long ts);
@Procedure(procedureName = "clear_alarm")
String clearAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long ts, @Param("a_details") String details);
@Procedure(procedureName = "assign_alarm")
String assignAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("u_id") UUID userId, @Param("a_ts") long assignTime);
@Procedure(procedureName = "unassign_alarm")
String unassignAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long unassignTime);
}

244
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -15,39 +15,54 @@
*/
package org.thingsboard.server.dao.sql.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmPropagationInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.sql.AlarmEntity;
import org.thingsboard.server.dao.model.sql.EntityAlarmEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.sql.query.AlarmQueryRepository;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@ -87,6 +102,15 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
return latest.isEmpty() ? null : DaoUtil.getData(latest.get(0));
}
@Override
public Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
List<AlarmEntity> latest = alarmRepository.findLatestActiveByOriginatorAndType(
originator.getId(),
type,
PageRequest.of(0, 1));
return latest.isEmpty() ? null : DaoUtil.getData(latest.get(0));
}
@Override
public ListenableFuture<Alarm> findLatestByOriginatorAndTypeAsync(TenantId tenantId, EntityId originator, String type) {
return service.submit(() -> findLatestByOriginatorAndType(tenantId, originator, type));
@ -97,6 +121,11 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
return findById(tenantId, key);
}
@Override
public AlarmInfo findAlarmInfoById(TenantId tenantId, UUID key) {
return DaoUtil.getData(alarmRepository.findAlarmInfoById(tenantId.getId(), key));
}
@Override
public ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, UUID key) {
return findByIdAsync(tenantId, key);
@ -106,11 +135,10 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
public PageData<AlarmInfo> findAlarms(TenantId tenantId, AlarmQuery query) {
log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink());
EntityId affectedEntity = query.getAffectedEntityId();
Set<AlarmStatus> statusSet = null;
if (query.getSearchStatus() != null) {
statusSet = query.getSearchStatus().getStatuses();
} else if (query.getStatus() != null) {
statusSet = Collections.singleton(query.getStatus());
AlarmStatusFilter asf = AlarmStatusFilter.from(query);
String assigneeId = null;
if (query.getAssigneeId() != null) {
assigneeId = query.getAssigneeId().toString();
}
if (affectedEntity != null) {
return DaoUtil.toPageData(
@ -120,7 +148,11 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
affectedEntity.getEntityType().name(),
query.getPageLink().getStartTime(),
query.getPageLink().getEndTime(),
statusSet,
asf.hasClearFilter(),
asf.hasClearFilter() && asf.getClearFilter(),
asf.hasAckFilter(),
asf.hasAckFilter() && asf.getAckFilter(),
assigneeId,
Objects.toString(query.getPageLink().getTextSearch(), ""),
DaoUtil.toPageable(query.getPageLink())
)
@ -131,7 +163,11 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
tenantId.getId(),
query.getPageLink().getStartTime(),
query.getPageLink().getEndTime(),
statusSet,
asf.hasClearFilter(),
asf.hasClearFilter() && asf.getClearFilter(),
asf.hasAckFilter(),
asf.hasAckFilter() && asf.getAckFilter(),
assigneeId,
Objects.toString(query.getPageLink().getTextSearch(), ""),
DaoUtil.toPageable(query.getPageLink())
)
@ -142,11 +178,10 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
@Override
public PageData<AlarmInfo> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) {
log.trace("Try to find customer alarms by status [{}] and pageLink [{}]", query.getStatus(), query.getPageLink());
Set<AlarmStatus> statusSet = null;
if (query.getSearchStatus() != null) {
statusSet = query.getSearchStatus().getStatuses();
} else if (query.getStatus() != null) {
statusSet = Collections.singleton(query.getStatus());
AlarmStatusFilter asf = AlarmStatusFilter.from(query);
String assigneeId = null;
if (query.getAssigneeId() != null) {
assigneeId = query.getAssigneeId().toString();
}
return DaoUtil.toPageData(
alarmRepository.findCustomerAlarms(
@ -154,7 +189,11 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
customerId.getId(),
query.getPageLink().getStartTime(),
query.getPageLink().getEndTime(),
statusSet,
asf.hasClearFilter(),
asf.hasClearFilter() && asf.getClearFilter(),
asf.hasAckFilter(),
asf.hasAckFilter() && asf.getAckFilter(),
assigneeId,
Objects.toString(query.getPageLink().getTextSearch(), ""),
DaoUtil.toPageable(query.getPageLink())
)
@ -167,8 +206,13 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
}
@Override
public Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> statuses) {
return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses);
public Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, AlarmStatusFilter asf, String assigneeId) {
return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(),
asf.hasClearFilter(),
asf.hasClearFilter() && asf.getClearFilter(),
asf.hasAckFilter(),
asf.hasAckFilter() && asf.getAckFilter(),
assigneeId);
}
@Override
@ -195,6 +239,174 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
entityAlarmRepository.deleteByEntityId(entityId.getId());
}
@Override
public AlarmApiCallResult createOrUpdateActiveAlarm(AlarmCreateOrUpdateActiveRequest request, boolean alarmCreationEnabled) {
AlarmPropagationInfo ap = getSafePropagationInfo(request.getPropagation());
return toAlarmApiResult(alarmRepository.createOrUpdateActiveAlarm(
request.getTenantId().getId(),
request.getCustomerId() != null ? request.getCustomerId().getId() : CustomerId.NULL_UUID,
UUID.randomUUID(),
System.currentTimeMillis(),
request.getOriginator().getId(),
request.getOriginator().getEntityType().ordinal(),
request.getType(),
request.getSeverity().name(),
request.getStartTs(), request.getEndTs(),
getDetailsAsString(request.getDetails()),
ap.isPropagate(),
ap.isPropagateToOwner(),
ap.isPropagateToTenant(),
getPropagationTypes(ap),
alarmCreationEnabled
));
}
@Override
public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
AlarmPropagationInfo ap = getSafePropagationInfo(request.getPropagation());
return toAlarmApiResult(alarmRepository.updateAlarm(
request.getTenantId().getId(),
request.getAlarmId().getId(),
request.getSeverity().name(),
request.getStartTs(), request.getEndTs(),
getDetailsAsString(request.getDetails()),
ap.isPropagate(),
ap.isPropagateToOwner(),
ap.isPropagateToTenant(),
getPropagationTypes(ap)
));
}
@Override
public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId id, long ackTs) {
return toAlarmApiResult(alarmRepository.acknowledgeAlarm(tenantId.getId(), id.getId(), ackTs));
}
@Override
public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId id, long clearTs, JsonNode details) {
return toAlarmApiResult(alarmRepository.clearAlarm(tenantId.getId(), id.getId(), clearTs, getDetailsAsString(details)));
}
@Override
public AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId id, UserId assigneeId, long assignTime) {
return toAlarmApiResult(alarmRepository.assignAlarm(tenantId.getId(), id.getId(), assigneeId.getId(), assignTime));
}
@Override
public AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId id, long unassignTime) {
return toAlarmApiResult(alarmRepository.unassignAlarm(tenantId.getId(), id.getId(), unassignTime));
}
@NotNull
private static String getPropagationTypes(AlarmPropagationInfo ap) {
String propagateRelationTypes;
if (!CollectionUtils.isEmpty(ap.getPropagateRelationTypes())) {
propagateRelationTypes = String.join(",", ap.getPropagateRelationTypes());
} else {
propagateRelationTypes = "";
}
return propagateRelationTypes;
}
private static AlarmPropagationInfo getSafePropagationInfo(AlarmPropagationInfo ap) {
return ap != null ? ap : AlarmPropagationInfo.EMPTY;
}
private static String getDetailsAsString(JsonNode details) {
var detailsStr = JacksonUtil.toString(details);
if (StringUtils.isEmpty(detailsStr)) {
detailsStr = "{}";
}
return detailsStr;
}
private AlarmApiCallResult toAlarmApiResult(String str) {
var json = JacksonUtil.toJsonNode(str);
var result = AlarmApiCallResult.builder();
boolean success = json.get("success").asBoolean();
result.successful(success);
if (success) {
boolean modified = false;
boolean created = false;
boolean cleared = false;
if (json.has("modified")) {
modified = json.get("modified").asBoolean();
}
if (json.has("created")) {
created = json.get("created").asBoolean();
}
if (json.has("cleared")) {
cleared = json.get("cleared").asBoolean();
}
result.created(created);
result.cleared(cleared);
result.modified(created || cleared || modified);
if (json.has("alarm") && !json.get("alarm").isNull()) {
result.alarm(toAlarmInfo(json.get("alarm")));
}
if (json.has("old") && !json.get("old").isNull()) {
result.old(toAlarm(json.get("old")));
}
}
return result.build();
}
private AlarmInfo toAlarmInfo(JsonNode json) {
AlarmInfo alarmInfo = new AlarmInfo(toAlarm(json));
getSafe(json, ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY).ifPresent(alarmInfo::setOriginatorName);
getSafe(json, ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY).ifPresent(alarmInfo::setOriginatorLabel);
if (alarmInfo.getAssigneeId() != null) {
var assigneeBuilder = AlarmAssignee.builder().id(alarmInfo.getAssigneeId());
getSafe(json, ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY).ifPresent(assigneeBuilder::firstName);
getSafe(json, ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY).ifPresent(assigneeBuilder::lastName);
getSafe(json, ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY).ifPresent(assigneeBuilder::email);
alarmInfo.setAssignee(assigneeBuilder.build());
}
return alarmInfo;
}
private Alarm toAlarm(JsonNode json) {
Alarm alarm = new Alarm(new AlarmId(UUID.fromString(json.get(ModelConstants.ID_PROPERTY).asText())));
alarm.setCreatedTime(json.get(ModelConstants.CREATED_TIME_PROPERTY).asLong());
getSafe(json, ModelConstants.TENANT_ID_COLUMN).ifPresent(s -> alarm.setTenantId(TenantId.fromUUID(UUID.fromString(s))));
getSafe(json, ModelConstants.CUSTOMER_ID_PROPERTY).ifPresent(s -> alarm.setCustomerId(new CustomerId(UUID.fromString(s))));
getSafe(json, ModelConstants.ASSIGNEE_ID_PROPERTY).ifPresent(s -> alarm.setAssigneeId(new UserId(UUID.fromString(s))));
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(
json.get(ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY).asInt(),
json.get(ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY).asText()));
getSafe(json, ModelConstants.ALARM_TYPE_PROPERTY).ifPresent(alarm::setType);
getSafe(json, ModelConstants.ALARM_SEVERITY_PROPERTY).map(AlarmSeverity::valueOf).ifPresent(alarm::setSeverity);
alarm.setAcknowledged(json.get(ModelConstants.ALARM_ACKNOWLEDGED_PROPERTY).asBoolean());
alarm.setCleared(json.get(ModelConstants.ALARM_CLEARED_PROPERTY).asBoolean());
alarm.setPropagate(json.get(ModelConstants.ALARM_PROPAGATE_PROPERTY).asBoolean());
alarm.setPropagateToOwner(json.get(ModelConstants.ALARM_PROPAGATE_TO_OWNER_PROPERTY).asBoolean());
alarm.setPropagateToTenant(json.get(ModelConstants.ALARM_PROPAGATE_TO_TENANT_PROPERTY).asBoolean());
alarm.setStartTs(json.get(ModelConstants.ALARM_START_TS_PROPERTY).asLong());
alarm.setEndTs(json.get(ModelConstants.ALARM_END_TS_PROPERTY).asLong());
alarm.setAckTs(json.get(ModelConstants.ALARM_ACK_TS_PROPERTY).asLong());
alarm.setClearTs(json.get(ModelConstants.ALARM_CLEAR_TS_PROPERTY).asLong());
alarm.setAssignTs(json.get(ModelConstants.ALARM_ASSIGN_TS_PROPERTY).asLong());
getSafe(json, ModelConstants.ALARM_DETAILS_PROPERTY).map(JacksonUtil::toJsonNode).ifPresent(alarm::setDetails);
alarm.setPropagateRelationTypes(getSafe(json, ModelConstants.ALARM_PROPAGATE_RELATION_TYPES).filter(StringUtils::isNoneEmpty)
.map(s -> Arrays.asList(s.split(","))).orElse(Collections.emptyList()));
return alarm;
}
private static Optional<String> getSafe(JsonNode json, String fieldName) {
if (json.has(fieldName)) {
var element = json.get(fieldName);
if (element.isNull() || !element.isTextual()) {
return Optional.empty();
} else {
return Optional.of(element.asText());
}
} else {
return Optional.empty();
}
}
@Override
public EntityType getEntityType() {
return EntityType.ALARM;

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

@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmId;
@ -28,6 +29,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
@ -68,6 +70,7 @@ public class AlarmDataAdapter {
alarm.setCreatedTime((long) row.get(ModelConstants.CREATED_TIME_PROPERTY));
alarm.setAckTs((long) row.get(ModelConstants.ALARM_ACK_TS_PROPERTY));
alarm.setClearTs((long) row.get(ModelConstants.ALARM_CLEAR_TS_PROPERTY));
alarm.setAssignTs((long) row.get(ModelConstants.ALARM_ASSIGN_TS_PROPERTY));
alarm.setStartTs((long) row.get(ModelConstants.ALARM_START_TS_PROPERTY));
alarm.setEndTs((long) row.get(ModelConstants.ALARM_END_TS_PROPERTY));
Object additionalInfo = row.get(ModelConstants.ADDITIONAL_INFO_PROPERTY);
@ -81,12 +84,23 @@ public class AlarmDataAdapter {
EntityType originatorType = EntityType.values()[(int) row.get(ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY)];
UUID originatorId = (UUID) row.get(ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY);
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId));
Object assigneeIdObj = row.get(ModelConstants.ASSIGNEE_ID_PROPERTY);
String assigneeFirstName = null;
String assigneeLastName = null;
String assigneeEmail = null;
if (assigneeIdObj != null) {
alarm.setAssigneeId(new UserId((UUID) row.get(ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY)));
assigneeFirstName = (String) row.get(ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY);
assigneeLastName = (String) row.get(ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY);
assigneeEmail = (String) row.get(ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY);
}
alarm.setPropagate((boolean) row.get(ModelConstants.ALARM_PROPAGATE_PROPERTY));
alarm.setPropagateToOwner((boolean) row.get(ModelConstants.ALARM_PROPAGATE_TO_OWNER_PROPERTY));
alarm.setPropagateToTenant((boolean) row.get(ModelConstants.ALARM_PROPAGATE_TO_TENANT_PROPERTY));
alarm.setType(row.get(ModelConstants.ALARM_TYPE_PROPERTY).toString());
alarm.setSeverity(AlarmSeverity.valueOf(row.get(ModelConstants.ALARM_SEVERITY_PROPERTY).toString()));
alarm.setStatus(AlarmStatus.valueOf(row.get(ModelConstants.ALARM_STATUS_PROPERTY).toString()));
alarm.setAcknowledged((boolean) row.get(ModelConstants.ALARM_ACKNOWLEDGED_PROPERTY));
alarm.setCleared((boolean) row.get(ModelConstants.ALARM_CLEARED_PROPERTY));
alarm.setTenantId(TenantId.fromUUID((UUID) row.get(ModelConstants.TENANT_ID_PROPERTY)));
Object customerIdObj = row.get(ModelConstants.CUSTOMER_ID_PROPERTY);
CustomerId customerId = customerIdObj != null ? new CustomerId((UUID) customerIdObj) : null;
@ -105,7 +119,17 @@ public class AlarmDataAdapter {
EntityId entityId = entityIdMap.get(entityUuid);
Object originatorNameObj = row.get(ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY);
String originatorName = originatorNameObj != null ? originatorNameObj.toString() : null;
return new AlarmData(alarm, originatorName, entityId);
Object originatorLabelObj = row.get(ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY);
String originatorLabel = originatorLabelObj != null ? originatorLabelObj.toString() : null;
AlarmData alarmData = new AlarmData(alarm, entityId);
alarmData.setOriginatorName(originatorName);
alarmData.setOriginatorLabel(originatorLabel);
if (alarm.getAssigneeId() != null) {
alarmData.setAssignee(new AlarmAssignee(alarm.getAssigneeId(), assigneeFirstName, assigneeLastName, assigneeEmail));
}
return alarmData;
}
}

125
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java

@ -19,11 +19,13 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -35,6 +37,7 @@ import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.dao.model.ModelConstants;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -51,46 +54,44 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
private static final Map<String, String> alarmFieldColumnMap = new HashMap<>();
private static final String ASSIGNEE_EMAIL_KEY = "assigneeEmail";
private static final String ASSIGNEE_LAST_NAME_KEY = "assigneeLastName";
private static final String ASSIGNEE_FIRST_NAME_KEY = "assigneeFirstName";
private static final String ASSIGNEE_ID_KEY = "assigneeId";
private static final String ASSIGNEE_KEY = "assignee";
static {
alarmFieldColumnMap.put("createdTime", ModelConstants.CREATED_TIME_PROPERTY);
alarmFieldColumnMap.put("ackTs", ModelConstants.ALARM_ACK_TS_PROPERTY);
alarmFieldColumnMap.put("ackTime", ModelConstants.ALARM_ACK_TS_PROPERTY);
alarmFieldColumnMap.put("clearTs", ModelConstants.ALARM_CLEAR_TS_PROPERTY);
alarmFieldColumnMap.put("clearTime", ModelConstants.ALARM_CLEAR_TS_PROPERTY);
alarmFieldColumnMap.put("assignTime", ModelConstants.ALARM_ASSIGN_TS_PROPERTY);
alarmFieldColumnMap.put("details", ModelConstants.ADDITIONAL_INFO_PROPERTY);
alarmFieldColumnMap.put("endTs", ModelConstants.ALARM_END_TS_PROPERTY);
alarmFieldColumnMap.put("endTime", ModelConstants.ALARM_END_TS_PROPERTY);
alarmFieldColumnMap.put("startTs", ModelConstants.ALARM_START_TS_PROPERTY);
alarmFieldColumnMap.put("startTime", ModelConstants.ALARM_START_TS_PROPERTY);
alarmFieldColumnMap.put("status", ModelConstants.ALARM_STATUS_PROPERTY);
alarmFieldColumnMap.put("acknowledged", ModelConstants.ALARM_ACKNOWLEDGED_PROPERTY);
alarmFieldColumnMap.put("cleared", ModelConstants.ALARM_CLEARED_PROPERTY);
alarmFieldColumnMap.put("type", ModelConstants.ALARM_TYPE_PROPERTY);
alarmFieldColumnMap.put("severity", ModelConstants.ALARM_SEVERITY_PROPERTY);
alarmFieldColumnMap.put("originatorId", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY);
alarmFieldColumnMap.put("originatorType", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY);
alarmFieldColumnMap.put("originator", "originator_name");
alarmFieldColumnMap.put(ASSIGNEE_ID_KEY, ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY);
alarmFieldColumnMap.put("originator", ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY);
alarmFieldColumnMap.put("originatorLabel", ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY);
alarmFieldColumnMap.put(ASSIGNEE_FIRST_NAME_KEY, ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY);
alarmFieldColumnMap.put(ASSIGNEE_LAST_NAME_KEY, ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY);
alarmFieldColumnMap.put(ASSIGNEE_EMAIL_KEY, ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY);
}
private static final String SELECT_ORIGINATOR_NAME = " COALESCE(CASE" +
" WHEN a.originator_type = " + EntityType.TENANT.ordinal() +
" THEN (select title from tenant where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.CUSTOMER.ordinal() +
" THEN (select title from customer where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.USER.ordinal() +
" THEN (select email from tb_user where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.DASHBOARD.ordinal() +
" THEN (select title from dashboard where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.ASSET.ordinal() +
" THEN (select name from asset where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.DEVICE.ordinal() +
" THEN (select name from device where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.ENTITY_VIEW.ordinal() +
" THEN (select name from entity_view where id = a.originator_id)" +
" END, 'Deleted') as originator_name";
private static final String FIELDS_SELECTION = "select a.id as id," +
" a.created_time as created_time," +
" a.ack_ts as ack_ts," +
" a.clear_ts as clear_ts," +
" a.assign_ts as assign_ts," +
" a.assignee_id as assignee_id," +
" a.additional_info as additional_info," +
" a.end_ts as end_ts," +
" a.originator_id as originator_id," +
@ -100,13 +101,19 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
" a.propagate_to_tenant as propagate_to_tenant," +
" a.severity as severity," +
" a.start_ts as start_ts," +
" a.status as status, " +
" a.tenant_id as tenant_id, " +
" a.customer_id as customer_id, " +
" a.propagate_relation_types as propagate_relation_types, " +
" a.type as type," + SELECT_ORIGINATOR_NAME + ", ";
" a.type as type, " +
" a.originator_name as originator_name, " +
" a.originator_label as originator_label, " +
" a.assignee_first_name as assignee_first_name, " +
" a.assignee_last_name as assignee_last_name, " +
" a.assignee_email as assignee_email, " +
" a.cleared as cleared, " +
" a.acknowledged as acknowledged, ";
private static final String JOIN_ENTITY_ALARMS = "inner join entity_alarm ea on a.id = ea.alarm_id";
private static final String JOIN_ENTITY_ALARMS = "inner join entity_alarm ea on a.id = ea.alarm_id ";
protected final NamedParameterJdbcTemplate jdbcTemplate;
private final TransactionTemplate transactionTemplate;
@ -121,12 +128,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
@Override
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
return transactionTemplate.execute(status -> {
return transactionTemplate.execute(trStatus -> {
AlarmDataPageLink pageLink = query.getPageLink();
QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, null, EntityType.ALARM));
ctx.addUuidListParameter("entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList()));
StringBuilder selectPart = new StringBuilder(FIELDS_SELECTION);
StringBuilder fromPart = new StringBuilder(" from alarm a ");
StringBuilder fromPart = new StringBuilder(" from alarm_info a ");
StringBuilder wherePart = new StringBuilder(" where ");
StringBuilder sortPart = new StringBuilder(" order by ");
StringBuilder joinPart = new StringBuilder();
@ -140,9 +147,25 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
selectPart.append(" a.originator_id as entity_id ");
}
EntityDataSortOrder sortOrder = pageLink.getSortOrder();
String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch());
List<EntityKey> alarmFields = new ArrayList<>();
for (EntityKey key : query.getAlarmFields()) {
if (EntityKeyType.ALARM_FIELD.equals(key.getType()) && ASSIGNEE_KEY.equalsIgnoreCase(key.getKey())) {
alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, ASSIGNEE_ID_KEY));
alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, ASSIGNEE_FIRST_NAME_KEY));
alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, ASSIGNEE_LAST_NAME_KEY));
alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, ASSIGNEE_EMAIL_KEY));
} else {
alarmFields.add(key);
}
}
String textSearchQuery = buildTextSearchQuery(ctx, alarmFields, pageLink.getTextSearch());
if (sortOrder != null && sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) {
String sortOrderKey = sortOrder.getKey().getKey();
if ("status".equalsIgnoreCase(sortOrderKey)) {
selectPart.append(", a.status as status ");
}
sortPart.append(alarmFieldColumnMap.getOrDefault(sortOrderKey, sortOrderKey))
.append(" ").append(sortOrder.getDirection().name());
if (pageLink.isSearchPropagatedAlarms()) {
@ -225,14 +248,25 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
wherePart.append("a.severity in (:alarmSeverities)");
}
if (pageLink.getStatusList() != null && !pageLink.getStatusList().isEmpty()) {
Set<AlarmStatus> statusSet = toStatusSet(pageLink.getStatusList());
if (!statusSet.isEmpty()) {
AlarmStatusFilter asf = AlarmStatusFilter.fromList(pageLink.getStatusList());
if (asf.hasAnyFilter()) {
if (asf.hasAckFilter()) {
addAndIfNeeded(wherePart, addAnd);
addAnd = true;
ctx.addStringListParameter("alarmStatuses", statusSet.stream().map(AlarmStatus::name).collect(Collectors.toList()));
wherePart.append(" a.status in (:alarmStatuses)");
ctx.addBooleanParameter("ackStatus", asf.getAckFilter());
wherePart.append(" a.acknowledged = :ackStatus");
}
if (asf.hasClearFilter()) {
addAndIfNeeded(wherePart, addAnd);
// addAnd = true; // not needed but stored as an example if someone adds new conditions
ctx.addBooleanParameter("clearStatus", asf.getClearFilter());
wherePart.append(" a.cleared = :clearStatus");
}
}
if (pageLink.getAssigneeId() != null) {
ctx.addUuidParameter("assigneeId", pageLink.getAssigneeId().getId());
wherePart.append(" a.assignee_id = :assigneeId");
}
String mainQuery = String.format("%s%s", selectPart, fromPart);
@ -295,37 +329,6 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
return permissionsQuery.toString();
}
private Set<AlarmStatus> toStatusSet(List<AlarmSearchStatus> statusList) {
Set<AlarmStatus> result = new HashSet<>();
for (AlarmSearchStatus searchStatus : statusList) {
switch (searchStatus) {
case ACK:
result.add(AlarmStatus.ACTIVE_ACK);
result.add(AlarmStatus.CLEARED_ACK);
break;
case UNACK:
result.add(AlarmStatus.ACTIVE_UNACK);
result.add(AlarmStatus.CLEARED_UNACK);
break;
case CLEARED:
result.add(AlarmStatus.CLEARED_ACK);
result.add(AlarmStatus.CLEARED_UNACK);
break;
case ACTIVE:
result.add(AlarmStatus.ACTIVE_ACK);
result.add(AlarmStatus.ACTIVE_UNACK);
break;
default:
break;
}
if (searchStatus == AlarmSearchStatus.ANY || result.size() == AlarmStatus.values().length) {
result.clear();
return result;
}
}
return result;
}
private void addAndIfNeeded(StringBuilder wherePart, boolean addAnd) {
if (addAnd) {
wherePart.append(" and ");

30
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -127,7 +127,8 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
userCredentials.setEnabled(false);
userCredentials.setActivateToken(generateSafeToken(DEFAULT_TOKEN_LENGTH));
userCredentials.setUserId(new UserId(savedUser.getUuidId()));
saveUserCredentialsAndPasswordHistory(user.getTenantId(), userCredentials);
userCredentials.setAdditionalInfo(JacksonUtil.newObjectNode());
userCredentialsDao.save(user.getTenantId(), userCredentials);
}
return savedUser;
}
@ -157,7 +158,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
public UserCredentials saveUserCredentials(TenantId tenantId, UserCredentials userCredentials) {
log.trace("Executing saveUserCredentials [{}]", userCredentials);
userCredentialsValidator.validate(userCredentials, data -> tenantId);
return saveUserCredentialsAndPasswordHistory(tenantId, userCredentials);
return userCredentialsDao.save(tenantId, userCredentials);
}
@Override
@ -175,7 +176,9 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
userCredentials.setEnabled(true);
userCredentials.setActivateToken(null);
userCredentials.setPassword(password);
if (userCredentials.getPassword() != null) {
updatePasswordHistory(userCredentials);
}
return saveUserCredentials(tenantId, userCredentials);
}
@ -211,7 +214,10 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
userCredentialsValidator.validate(userCredentials, data -> tenantId);
userCredentialsDao.removeById(tenantId, userCredentials.getUuidId());
userCredentials.setId(null);
return saveUserCredentialsAndPasswordHistory(tenantId, userCredentials);
if (userCredentials.getPassword() != null) {
updatePasswordHistory(userCredentials);
}
return userCredentialsDao.save(tenantId, userCredentials);
}
@Override
@ -347,17 +353,8 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
return failedLoginAttempts;
}
private UserCredentials saveUserCredentialsAndPasswordHistory(TenantId tenantId, UserCredentials userCredentials) {
UserCredentials result = userCredentialsDao.save(tenantId, userCredentials);
User user = findUserById(tenantId, userCredentials.getUserId());
if (userCredentials.getPassword() != null) {
updatePasswordHistory(user, userCredentials);
}
return result;
}
private void updatePasswordHistory(User user, UserCredentials userCredentials) {
JsonNode additionalInfo = user.getAdditionalInfo();
private void updatePasswordHistory(UserCredentials userCredentials) {
JsonNode additionalInfo = userCredentials.getAdditionalInfo();
if (!(additionalInfo instanceof ObjectNode)) {
additionalInfo = JacksonUtil.newObjectNode();
}
@ -378,8 +375,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
userPasswordHistoryJson = JacksonUtil.valueToTree(userPasswordHistoryMap);
((ObjectNode) additionalInfo).set(USER_PASSWORD_HISTORY, userPasswordHistoryJson);
}
user.setAdditionalInfo(additionalInfo);
saveUser(user);
userCredentials.setAdditionalInfo(additionalInfo);
}
private final PaginatedRemover<TenantId, User> tenantAdminsRemover = new PaginatedRemover<>() {

10
dao/src/main/resources/sql/schema-entities-idx.sql

@ -20,12 +20,20 @@ CREATE INDEX IF NOT EXISTS idx_alarm_originator_created_time ON alarm(originator
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_status_created_time ON alarm(tenant_id, status, created_time DESC);
-- Drop index by 'status' column and replace with new one that has only active alarms;
CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type_active
ON alarm USING btree (originator_id, type) WHERE cleared = false;
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_created_time ON alarm(tenant_id, type, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_assignee_created_time ON alarm(tenant_id, assignee_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_entity_alarm_created_time ON entity_alarm(tenant_id, entity_id, created_time DESC);
-- Cover index by alarm type to optimize propagated alarm queries;
CREATE INDEX IF NOT EXISTS idx_entity_alarm_entity_id_alarm_type_created_time_alarm_id ON entity_alarm
USING btree (tenant_id, entity_id, alarm_type, created_time DESC) INCLUDE(alarm_id);
CREATE INDEX IF NOT EXISTS idx_entity_alarm_alarm_id ON entity_alarm(alarm_id);
CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id);

259
dao/src/main/resources/sql/schema-entities.sql

@ -52,13 +52,16 @@ CREATE TABLE IF NOT EXISTS alarm (
propagate boolean,
severity varchar(255),
start_ts bigint,
status varchar(255),
assign_ts bigint,
assignee_id uuid,
tenant_id uuid,
customer_id uuid,
propagate_relation_types varchar,
type varchar(255),
propagate_to_owner boolean,
propagate_to_tenant boolean
propagate_to_tenant boolean,
acknowledged boolean,
cleared boolean
);
CREATE TABLE IF NOT EXISTS alarm_comment (
@ -427,13 +430,6 @@ CREATE TABLE IF NOT EXISTS relation (
additional_info varchar,
CONSTRAINT relation_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type)
);
-- ) PARTITION BY LIST (relation_type_group);
--
-- CREATE TABLE other_relations PARTITION OF relation DEFAULT;
-- CREATE TABLE common_relations PARTITION OF relation FOR VALUES IN ('COMMON');
-- CREATE TABLE alarm_relations PARTITION OF relation FOR VALUES IN ('ALARM');
-- CREATE TABLE dashboard_relations PARTITION OF relation FOR VALUES IN ('DASHBOARD');
-- CREATE TABLE rule_relations PARTITION OF relation FOR VALUES IN ('RULE_CHAIN', 'RULE_NODE');
CREATE TABLE IF NOT EXISTS tb_user (
id uuid NOT NULL CONSTRAINT tb_user_pkey PRIMARY KEY,
@ -488,7 +484,8 @@ CREATE TABLE IF NOT EXISTS user_credentials (
enabled boolean,
password varchar(255),
reset_token varchar(255) UNIQUE,
user_id uuid UNIQUE
user_id uuid UNIQUE,
additional_info varchar DEFAULT '{}'
);
CREATE TABLE IF NOT EXISTS widget_type (
@ -858,3 +855,245 @@ CREATE TABLE IF NOT EXISTS user_settings (
settings varchar(10000),
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE
);
DROP VIEW IF EXISTS alarm_info CASCADE;
CREATE VIEW alarm_info AS
SELECT a.*,
(CASE WHEN a.acknowledged AND a.cleared THEN 'CLEARED_ACK'
WHEN NOT a.acknowledged AND a.cleared THEN 'CLEARED_UNACK'
WHEN a.acknowledged AND NOT a.cleared THEN 'ACTIVE_ACK'
WHEN NOT a.acknowledged AND NOT a.cleared THEN 'ACTIVE_UNACK' END) as status,
COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id)
WHEN a.originator_type = 1 THEN (select title 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 = 3 THEN (select title from dashboard where id = a.originator_id)
WHEN a.originator_type = 4 THEN (select name from asset where id = a.originator_id)
WHEN a.originator_type = 5 THEN (select 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 = 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 = 18 THEN (select name from edge where id = a.originator_id) END
, 'Deleted') originator_name,
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 = 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 = 4 THEN (select COALESCE(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 = 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 = 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
, 'Deleted') as originator_label,
u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email
FROM alarm a
LEFT JOIN tb_user u ON u.id = a.assignee_id;
CREATE OR REPLACE FUNCTION create_or_update_active_alarm(
t_id uuid, c_id uuid, a_id uuid, a_created_ts bigint,
a_o_id uuid, a_o_type integer, a_type varchar,
a_severity varchar, a_start_ts bigint, a_end_ts bigint,
a_details varchar,
a_propagate boolean, a_propagate_to_owner boolean,
a_propagate_to_tenant boolean, a_propagation_types varchar,
a_creation_enabled boolean)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
null_id constant uuid = '13814000-1dd2-11b2-8080-808080808080'::uuid;
existing alarm;
result alarm_info;
row_count integer;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.originator_id = a_o_id AND a.type = a_type AND a.cleared = false ORDER BY a.start_ts DESC FOR UPDATE;
IF existing.id IS NULL THEN
IF a_creation_enabled = FALSE THEN
RETURN json_build_object('success', false)::text;
END IF;
IF c_id = null_id THEN
c_id = NULL;
end if;
INSERT INTO alarm
(tenant_id, customer_id, id, created_time,
originator_id, originator_type, type,
severity, start_ts, end_ts,
additional_info,
propagate, propagate_to_owner, propagate_to_tenant, propagate_relation_types,
acknowledged, ack_ts,
cleared, clear_ts,
assignee_id, assign_ts)
VALUES
(t_id, c_id, a_id, a_created_ts,
a_o_id, a_o_type, a_type,
a_severity, a_start_ts, a_end_ts,
a_details,
a_propagate, a_propagate_to_owner, a_propagate_to_tenant, a_propagation_types,
false, 0, false, 0, NULL, 0);
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'created', true, 'modified', true, 'alarm', row_to_json(result))::text;
ELSE
UPDATE alarm a
SET severity = a_severity,
start_ts = a_start_ts,
end_ts = a_end_ts,
additional_info = a_details,
propagate = a_propagate,
propagate_to_owner = a_propagate_to_owner,
propagate_to_tenant = a_propagate_to_tenant,
propagate_relation_types = a_propagation_types
WHERE a.id = existing.id
AND a.tenant_id = t_id
AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
GET DIAGNOSTICS row_count = ROW_COUNT;
SELECT * INTO result FROM alarm_info a WHERE a.id = existing.id AND a.tenant_id = t_id;
IF row_count > 0 THEN
RETURN json_build_object('success', true, 'modified', true, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
ELSE
RETURN json_build_object('success', true, 'modified', false, 'alarm', row_to_json(result))::text;
END IF;
END IF;
END
$$;
DROP FUNCTION IF EXISTS update_alarm;
CREATE OR REPLACE FUNCTION update_alarm(t_id uuid, a_id uuid, a_severity varchar, a_start_ts bigint, a_end_ts bigint,
a_details varchar,
a_propagate boolean, a_propagate_to_owner boolean,
a_propagate_to_tenant boolean, a_propagation_types varchar)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
row_count integer;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
UPDATE alarm a
SET severity = a_severity,
start_ts = a_start_ts,
end_ts = a_end_ts,
additional_info = a_details,
propagate = a_propagate,
propagate_to_owner = a_propagate_to_owner,
propagate_to_tenant = a_propagate_to_tenant,
propagate_relation_types = a_propagation_types
WHERE a.id = a_id
AND a.tenant_id = t_id
AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
GET DIAGNOSTICS row_count = ROW_COUNT;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
IF row_count > 0 THEN
RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
ELSE
RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result))::text;
END IF;
END
$$;
DROP FUNCTION IF EXISTS acknowledge_alarm;
CREATE OR REPLACE FUNCTION acknowledge_alarm(t_id uuid, a_id uuid, a_ts bigint)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
modified boolean = FALSE;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF NOT (existing.acknowledged) THEN
modified = TRUE;
UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
END
$$;
DROP FUNCTION IF EXISTS clear_alarm;
CREATE OR REPLACE FUNCTION clear_alarm(t_id uuid, a_id uuid, a_ts bigint, a_details varchar)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
cleared boolean = FALSE;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF NOT(existing.cleared) THEN
cleared = TRUE;
UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text;
END
$$;
DROP FUNCTION IF EXISTS assign_alarm;
CREATE OR REPLACE FUNCTION assign_alarm(t_id uuid, a_id uuid, u_id uuid, a_ts bigint)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
modified boolean = FALSE;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF existing.assignee_id IS NULL OR existing.assignee_id != u_id THEN
modified = TRUE;
UPDATE alarm a SET assignee_id = u_id, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
END
$$;
DROP FUNCTION IF EXISTS unassign_alarm;
CREATE OR REPLACE FUNCTION unassign_alarm(t_id uuid, a_id uuid, a_ts bigint)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
modified boolean = FALSE;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF existing.assignee_id IS NOT NULL THEN
modified = TRUE;
UPDATE alarm a SET assignee_id = NULL, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
END
$$;

2
dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java

@ -58,7 +58,7 @@ public abstract class BaseAlarmCommentServiceTest extends AbstractServiceTest {
alarm = Alarm.builder().tenantId(tenantId).originator(new AssetId(Uuids.timeBased()))
.type(TEST_ALARM)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.startTs(System.currentTimeMillis()).build();
alarm = alarmService.createOrUpdateAlarm(alarm).getAlarm();

320
dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java

@ -20,20 +20,25 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmPropagationInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.query.AlarmData;
@ -45,8 +50,9 @@ import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.common.util.JacksonUtil;
import java.util.Arrays;
import java.util.Collections;
@ -56,6 +62,11 @@ import java.util.concurrent.ExecutionException;
public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
public static final String TEST_ALARM = "TEST_ALARM";
private static final String TEST_TENANT_EMAIL = "testtenant@thingsboard.org";
private static final String TEST_TENANT_FIRST_NAME = "testtenantfirstname";
private static final String TEST_TENANT_LAST_NAME = "testtenantlastname";
private TenantId tenantId;
@Before
@ -83,12 +94,12 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertTrue(relationService.saveRelationAsync(tenantId, relation).get());
long ts = System.currentTimeMillis();
Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId)
AlarmApiCallResult result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(childId)
.type(TEST_ALARM)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm);
.severity(AlarmSeverity.CRITICAL)
.startTs(ts).build());
Alarm created = result.getAlarm();
Assert.assertNotNull(created);
@ -107,7 +118,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(0L, created.getAckTs());
Assert.assertEquals(0L, created.getClearTs());
Alarm fetched = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get();
Alarm fetched = alarmService.findAlarmInfoById(tenantId, created.getId());
Assert.assertEquals(created, fetched);
}
@ -121,14 +132,13 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertTrue(relationService.saveRelationAsync(tenantId, relation).get());
long ts = System.currentTimeMillis();
Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId)
AlarmApiCallResult result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(childId)
.type(TEST_ALARM)
.propagate(false)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm);
Alarm created = result.getAlarm();
.severity(AlarmSeverity.CRITICAL)
.startTs(ts).build());
AlarmInfo created = result.getAlarm();
// Check child relation
PageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -139,7 +149,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
// Check parent relation
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -152,7 +162,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(0, alarms.getData().size());
created.setPropagate(true);
result = alarmService.createOrUpdateAlarm(created);
result = alarmService.updateAlarm(AlarmUpdateRequest.fromAlarm(created));
created = result.getAlarm();
// Check child relation
@ -164,7 +174,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
// Check parent relation
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -175,10 +185,10 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
alarmService.ackAlarm(tenantId, created.getId(), System.currentTimeMillis()).get();
created = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get();
alarmService.acknowledgeAlarm(tenantId, created.getId(), System.currentTimeMillis());
created = alarmService.findAlarmInfoById(tenantId, created.getId());
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(childId)
@ -188,7 +198,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
// Check not existing relation
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -200,8 +210,8 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(0, alarms.getData().size());
alarmService.clearAlarm(tenantId, created.getId(), null, System.currentTimeMillis()).get();
created = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get();
alarmService.clearAlarm(tenantId, created.getId(), System.currentTimeMillis(), null);
created = alarmService.findAlarmInfoById(tenantId, created.getId());
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(childId)
@ -211,7 +221,77 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
}
@Test
public void testFindAssignedAlarm() throws ExecutionException, InterruptedException {
AssetId parentId = new AssetId(Uuids.timeBased());
AssetId childId = new AssetId(Uuids.timeBased());
EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
Assert.assertTrue(relationService.saveRelation(tenantId, relation));
long ts = System.currentTimeMillis();
AlarmApiCallResult result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(childId)
.type(TEST_ALARM)
.severity(AlarmSeverity.CRITICAL)
.startTs(ts).build());
AlarmInfo created = result.getAlarm();
User tenantUser = new User();
tenantUser.setTenantId(tenantId);
tenantUser.setAuthority(Authority.TENANT_ADMIN);
tenantUser.setEmail(TEST_TENANT_EMAIL);
tenantUser.setFirstName(TEST_TENANT_FIRST_NAME);
tenantUser.setLastName(TEST_TENANT_LAST_NAME);
tenantUser = userService.saveUser(tenantUser);
Assert.assertNotNull(tenantUser);
AlarmApiCallResult assignmentResult = alarmService.assignAlarm(tenantId, created.getId(), tenantUser.getId(), ts);
created = assignmentResult.getAlarm();
PageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.assigneeId(tenantUser.getId())
.fetchOriginator(true)
.pageLink(new TimePageLink(1, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
AlarmDataPageLink pageLink = new AlarmDataPageLink();
pageLink.setPage(0);
pageLink.setPageSize(10);
pageLink.setAssigneeId(tenantUser.getId());
PageData<AlarmData> assignedAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(created.getOriginator()));
Assert.assertNotNull(assignedAlarms.getData());
Assert.assertEquals(1, assignedAlarms.getData().size());
Assert.assertEquals(created, new AlarmInfo(assignedAlarms.getData().get(0)));
User tenantUser2 = new User();
tenantUser2.setTenantId(tenantId);
tenantUser2.setAuthority(Authority.TENANT_ADMIN);
tenantUser2.setEmail(2 + TEST_TENANT_EMAIL);
tenantUser2.setFirstName(TEST_TENANT_FIRST_NAME);
tenantUser2.setLastName(TEST_TENANT_LAST_NAME);
tenantUser2 = userService.saveUser(tenantUser2);
Assert.assertNotNull(tenantUser2);
pageLink.setAssigneeId(tenantUser2.getId());
PageData<AlarmData> assignedToNonExistingUserAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(created.getOriginator()));
Assert.assertNotNull(assignedToNonExistingUserAlarms.getData());
Assert.assertTrue(assignedToNonExistingUserAlarms.getData().isEmpty());
}
@Test
@ -235,23 +315,23 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
customerDevice = deviceService.saveDevice(customerDevice);
long ts = System.currentTimeMillis();
Alarm tenantAlarm = Alarm.builder().tenantId(tenantId)
AlarmApiCallResult result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(tenantDevice.getId())
.type(TEST_ALARM)
.propagate(true)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm);
tenantAlarm = result.getAlarm();
.severity(AlarmSeverity.CRITICAL)
.propagation(AlarmPropagationInfo.builder().propagate(true).build())
.startTs(ts).build());
AlarmInfo tenantAlarm = result.getAlarm();
Alarm deviceAlarm = Alarm.builder().tenantId(tenantId)
result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(customerDevice.getId())
.type(TEST_ALARM)
.propagate(true)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
result = alarmService.createOrUpdateAlarm(deviceAlarm);
deviceAlarm = result.getAlarm();
.severity(AlarmSeverity.CRITICAL)
.propagation(AlarmPropagationInfo.builder().propagate(true).build())
.startTs(ts).build());
AlarmInfo deviceAlarm = result.getAlarm();
AlarmDataPageLink pageLink = new AlarmDataPageLink();
pageLink.setPage(0);
@ -269,7 +349,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
PageData<AlarmData> customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customerDevice.getId()));
Assert.assertEquals(1, customerAlarms.getData().size());
Assert.assertEquals(deviceAlarm, customerAlarms.getData().get(0));
Assert.assertEquals(deviceAlarm, new AlarmInfo(customerAlarms.getData().get(0)));
PageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(tenantDevice.getId())
@ -279,7 +359,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(tenantAlarm, alarms.getData().get(0));
Assert.assertEquals(tenantAlarm, new AlarmInfo(alarms.getData().get(0)));
}
@Test
@ -311,23 +391,21 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
relationService.saveRelation(tenantId, relation);
long ts = System.currentTimeMillis();
Alarm tenantAlarm = Alarm.builder().tenantId(tenantId)
alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(tenantDevice.getId())
.type("Not Propagated")
.propagate(false)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm);
tenantAlarm = result.getAlarm();
.severity(AlarmSeverity.CRITICAL)
.startTs(ts).build());
Alarm customerAlarm = Alarm.builder().tenantId(tenantId)
AlarmApiCallResult result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(tenantDevice.getId())
.type("Propagated")
.propagate(true)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
result = alarmService.createOrUpdateAlarm(customerAlarm);
customerAlarm = result.getAlarm();
.severity(AlarmSeverity.CRITICAL)
.propagation(AlarmPropagationInfo.builder().propagate(true).build())
.startTs(ts).build());
AlarmInfo customerAlarm = result.getAlarm();
AlarmDataPageLink pageLink = new AlarmDataPageLink();
pageLink.setPage(0);
@ -343,7 +421,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
//TEST that propagated alarms are visible on the asset level.
PageData<AlarmData> customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customerAsset.getId()));
Assert.assertEquals(1, customerAlarms.getData().size());
Assert.assertEquals(customerAlarm, customerAlarms.getData().get(0));
Assert.assertEquals(customerAlarm, new AlarmInfo(customerAlarms.getData().get(0)));
}
@Test
@ -361,24 +439,24 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
device = deviceService.saveDevice(device);
long ts = System.currentTimeMillis();
Alarm tenantAlarm = Alarm.builder().tenantId(tenantId)
AlarmApiCallResult result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(device.getId())
.type("Propagated To Tenant")
.propagateToTenant(true)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm);
tenantAlarm = result.getAlarm();
.severity(AlarmSeverity.CRITICAL)
.propagation(AlarmPropagationInfo.builder().propagateToTenant(true).build())
.startTs(ts).build());
AlarmInfo tenantAlarm = result.getAlarm();
Alarm customerAlarm = Alarm.builder().tenantId(tenantId)
result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(device.getId())
.type("Propagated to Customer")
.propagate(false)
.propagateToOwner(true)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
result = alarmService.createOrUpdateAlarm(customerAlarm);
customerAlarm = result.getAlarm();
.severity(AlarmSeverity.CRITICAL)
.propagation(AlarmPropagationInfo.builder().propagateToOwner(true).build())
.startTs(ts).build());
AlarmInfo customerAlarm = result.getAlarm();
AlarmDataPageLink pageLink = new AlarmDataPageLink();
pageLink.setPage(0);
@ -389,24 +467,24 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setEndTs(System.currentTimeMillis());
pageLink.setSearchPropagatedAlarms(true);
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
pageLink.setStatusList(Collections.singletonList(AlarmSearchStatus.ACTIVE));
//TEST that propagated alarms are visible on the asset level.
PageData<AlarmData> tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(tenantId));
Assert.assertEquals(1, tenantAlarms.getData().size());
Assert.assertEquals(tenantAlarm, tenantAlarms.getData().get(0));
Assert.assertEquals(tenantAlarm, new AlarmInfo(tenantAlarms.getData().get(0)));
//TEST that propagated alarms are visible on the asset level.
PageData<AlarmData> customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customer.getId()));
Assert.assertEquals(1, customerAlarms.getData().size());
Assert.assertEquals(customerAlarm, customerAlarms.getData().get(0));
Assert.assertEquals(customerAlarm, new AlarmInfo(customerAlarms.getData().get(0)));
}
private AlarmDataQuery toQuery(AlarmDataPageLink pageLink){
private AlarmDataQuery toQuery(AlarmDataPageLink pageLink) {
return toQuery(pageLink, Collections.emptyList());
}
private AlarmDataQuery toQuery(AlarmDataPageLink pageLink, List<EntityKey> alarmFields){
private AlarmDataQuery toQuery(AlarmDataPageLink pageLink, List<EntityKey> alarmFields) {
return new AlarmDataQuery(new DeviceTypeFilter(), pageLink, null, null, null, alarmFields);
}
@ -425,45 +503,41 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
customerDevice = deviceService.saveDevice(customerDevice);
// no one alarms was created
Assert.assertNull(alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null));
Assert.assertNull(alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null, null));
Alarm alarm1 = Alarm.builder()
AlarmApiCallResult result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(customerDevice.getId())
.type(TEST_ALARM)
.severity(AlarmSeverity.MAJOR)
.status(AlarmStatus.ACTIVE_UNACK)
.startTs(System.currentTimeMillis())
.build();
alarm1 = alarmService.createOrUpdateAlarm(alarm1).getAlarm();
alarmService.clearAlarm(tenantId, alarm1.getId(), null, System.currentTimeMillis()).get();
.startTs(System.currentTimeMillis()).build());
AlarmInfo alarm1 = result.getAlarm();
alarmService.clearAlarm(tenantId, alarm1.getId(), System.currentTimeMillis(), null);
Alarm alarm2 = Alarm.builder()
result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(customerDevice.getId())
.type(TEST_ALARM)
.severity(AlarmSeverity.MINOR)
.status(AlarmStatus.ACTIVE_ACK)
.startTs(System.currentTimeMillis())
.build();
alarm2 = alarmService.createOrUpdateAlarm(alarm2).getAlarm();
alarmService.clearAlarm(tenantId, alarm2.getId(), null, System.currentTimeMillis()).get();
.startTs(System.currentTimeMillis()).build());
AlarmInfo alarm2 = result.getAlarm();
alarmService.acknowledgeAlarm(tenantId, alarm2.getId(), System.currentTimeMillis());
alarmService.clearAlarm(tenantId, alarm2.getId(), System.currentTimeMillis(), null);
Alarm alarm3 = Alarm.builder()
result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(customerDevice.getId())
.type(TEST_ALARM)
.severity(AlarmSeverity.CRITICAL)
.status(AlarmStatus.ACTIVE_ACK)
.startTs(System.currentTimeMillis())
.build();
alarm3 = alarmService.createOrUpdateAlarm(alarm3).getAlarm();
Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.UNACK, null));
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null));
Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_UNACK));
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.ACTIVE, null));
Assert.assertEquals(AlarmSeverity.MINOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_ACK));
.startTs(System.currentTimeMillis()).build());
AlarmInfo alarm3 = result.getAlarm();
alarmService.acknowledgeAlarm(tenantId, alarm3.getId(), System.currentTimeMillis());
Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.UNACK, null, null));
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null, null));
Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_UNACK, null));
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.ACTIVE, null, null));
Assert.assertEquals(AlarmSeverity.MINOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_ACK, null));
}
@Test
@ -479,15 +553,13 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertTrue(relationService.saveRelationAsync(tenantId, relation2).get());
long ts = System.currentTimeMillis();
Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId)
AlarmApiCallResult result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(childId)
.type(TEST_ALARM)
.propagate(false)
.severity(AlarmSeverity.CRITICAL)
.status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm);
Alarm created = result.getAlarm();
.startTs(ts).build());
AlarmInfo created = result.getAlarm();
AlarmDataPageLink pageLink = new AlarmDataPageLink();
pageLink.setPage(0);
@ -504,7 +576,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
pageLink.setPage(0);
pageLink.setPageSize(10);
@ -519,17 +591,17 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
pageLink.setSearchPropagatedAlarms(true);
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
// Check child relation
created.setPropagate(true);
result = alarmService.createOrUpdateAlarm(created);
result = alarmService.updateAlarm(AlarmUpdateRequest.fromAlarm(created));
created = result.getAlarm();
// Check child relation
@ -546,7 +618,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
// Check parent relation
pageLink.setPage(0);
@ -562,37 +634,40 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(parentId));
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
PageData<AlarmInfo> alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(childId)
.fetchOriginator(true)
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
new TimePageLink(10, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build()).get();
Assert.assertNotNull(alarmsInfoData.getData());
Assert.assertEquals(1, alarmsInfoData.getData().size());
Assert.assertEquals(created, alarmsInfoData.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarmsInfoData.getData().get(0)));
alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(parentId)
.fetchOriginator(true)
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
new TimePageLink(10, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build()).get();
Assert.assertNotNull(alarmsInfoData.getData());
Assert.assertEquals(1, alarmsInfoData.getData().size());
Assert.assertEquals(created, alarmsInfoData.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarmsInfoData.getData().get(0)));
alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(parentId2)
.fetchOriginator(true)
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
new TimePageLink(10, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build()).get();
Assert.assertNotNull(alarmsInfoData.getData());
Assert.assertEquals(1, alarmsInfoData.getData().size());
Assert.assertEquals(created, alarmsInfoData.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarmsInfoData.getData().get(0)));
pageLink.setPage(0);
pageLink.setPageSize(10);
@ -607,10 +682,9 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(parentId));
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
alarmService.ackAlarm(tenantId, created.getId(), System.currentTimeMillis()).get();
created = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get();
created = alarmService.acknowledgeAlarm(tenantId, created.getId(), System.currentTimeMillis()).getAlarm();
pageLink.setPage(0);
pageLink.setPageSize(10);
@ -625,7 +699,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
}
@Test
@ -635,17 +709,17 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
Assert.assertTrue(relationService.saveRelationAsync(tenantId, relation).get());
Assert.assertTrue(relationService.saveRelation(tenantId, relation));
long ts = System.currentTimeMillis();
Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId)
AlarmApiCallResult result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(childId)
.type(TEST_ALARM)
.propagate(true)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm);
Alarm created = result.getAlarm();
.severity(AlarmSeverity.CRITICAL)
.propagation(AlarmPropagationInfo.builder().propagate(true).build())
.startTs(ts).build());
AlarmInfo created = result.getAlarm();
PageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(childId)
@ -655,7 +729,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
// Check parent relation
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -666,7 +740,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0)));
Assert.assertTrue("Alarm was not deleted when expected", alarmService.deleteAlarm(tenantId, created.getId()).isSuccessful());

1
dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java

@ -76,7 +76,6 @@ public class JpaAlarmCommentDaoTest extends AbstractJpaDaoTest {
alarm.setPropagate(true);
alarm.setStartTs(System.currentTimeMillis());
alarm.setEndTs(System.currentTimeMillis());
alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
alarmDao.save(TenantId.fromUUID(tenantId), alarm);
}
private void saveAlarmComment(UUID id, UUID alarmId, UUID userId, AlarmCommentType type) {

244
dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java

@ -19,21 +19,31 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
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.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.AbstractJpaDaoTest;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmDao;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Created by Valerii Sosliuk on 5/21/2017.
@ -48,28 +58,235 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
@Test
public void testFindLatestByOriginatorAndType() throws ExecutionException, InterruptedException, TimeoutException {
log.info("Current system time in millis = {}", System.currentTimeMillis());
UUID tenantId = UUID.fromString("d4b68f40-3e96-11e7-a884-898080180d6b");
TenantId tenantId = TenantId.fromUUID(UUID.randomUUID());
UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID originator2Id = UUID.fromString("d4b68f42-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
UUID alarm2Id = UUID.fromString("d4b68f44-3e96-11e7-a884-898080180d6b");
UUID alarm3Id = UUID.fromString("d4b68f45-3e96-11e7-a884-898080180d6b");
int alarmCountBeforeSave = alarmDao.find(TenantId.fromUUID(tenantId)).size();
saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
// The find method does not filter by tenant. It is just using the tenantId for rate limits if any.
var alarmsBeforeSave = alarmDao.find(tenantId).stream().filter(a -> a.getTenantId().equals(tenantId)).collect(Collectors.toList());
int alarmCountBeforeSave = alarmsBeforeSave.size();
saveAlarm(alarm1Id, tenantId.getId(), originator1Id, "TEST_ALARM");
//The timestamp of the startTime should be different in order for test to always work
Thread.sleep(1);
saveAlarm(alarm2Id, tenantId, originator1Id, "TEST_ALARM");
saveAlarm(alarm3Id, tenantId, originator2Id, "TEST_ALARM");
int alarmCountAfterSave = alarmDao.find(TenantId.fromUUID(tenantId)).size();
assertEquals(3, alarmCountAfterSave - alarmCountBeforeSave);
saveAlarm(alarm2Id, tenantId.getId(), originator1Id, "TEST_ALARM");
saveAlarm(alarm3Id, tenantId.getId(), originator2Id, "TEST_ALARM");
var alarmsAfterSave = alarmDao.find(tenantId).stream().filter(a -> a.getTenantId().equals(tenantId)).collect(Collectors.toList());
int alarmCountAfterSave = alarmsAfterSave.size();
int diff = alarmCountAfterSave - alarmCountBeforeSave;
if (diff != 3) {
System.out.println("test");
}
assertEquals(3, diff);
ListenableFuture<Alarm> future = alarmDao
.findLatestByOriginatorAndTypeAsync(TenantId.fromUUID(tenantId), new DeviceId(originator1Id), "TEST_ALARM");
.findLatestByOriginatorAndTypeAsync(tenantId, new DeviceId(originator1Id), "TEST_ALARM");
Alarm alarm = future.get(30, TimeUnit.SECONDS);
assertNotNull(alarm);
assertEquals(alarm2Id, alarm.getId().getId());
}
private void saveAlarm(UUID id, UUID tenantId, UUID deviceId, String type) {
@Test
public void createOrUpdateActiveAlarm() {
TenantId tenantId = TenantId.fromUUID(UUID.randomUUID());
DeviceId deviceId = new DeviceId(UUID.randomUUID());
AlarmCreateOrUpdateActiveRequest request = AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(deviceId)
.type("ALARM_TYPE")
.severity(AlarmSeverity.MAJOR)
.build();
AlarmApiCallResult result = alarmDao.createOrUpdateActiveAlarm(request, true);
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isCreated());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
UUID newAlarmId = result.getAlarm().getUuidId();
AlarmInfo afterSave = alarmDao.findAlarmInfoById(tenantId, newAlarmId);
assertEquals(afterSave, result.getAlarm());
request = AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(deviceId)
.type("ALARM_TYPE")
.severity(AlarmSeverity.CRITICAL)
.build();
result = alarmDao.createOrUpdateActiveAlarm(request, true);
assertNotNull(result);
assertTrue(result.isSuccessful());
assertFalse(result.isCreated());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(newAlarmId, result.getAlarm().getUuidId());
afterSave = alarmDao.findAlarmInfoById(tenantId, newAlarmId);
assertEquals(afterSave, result.getAlarm());
alarmDao.clearAlarm(tenantId, result.getAlarm().getId(), System.currentTimeMillis(), result.getAlarm().getDetails());
request = AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(deviceId)
.type("ALARM_TYPE")
.severity(AlarmSeverity.CRITICAL)
.build();
result = alarmDao.createOrUpdateActiveAlarm(request, true);
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isCreated());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertNotEquals(newAlarmId, result.getAlarm().getUuidId());
afterSave = alarmDao.findAlarmInfoById(tenantId, result.getAlarm().getUuidId());
assertEquals(afterSave, result.getAlarm());
alarmDao.clearAlarm(tenantId, result.getAlarm().getId(), System.currentTimeMillis(), result.getAlarm().getDetails());
request = AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(deviceId)
.type("ALARM_TYPE2")
.severity(AlarmSeverity.CRITICAL)
.build();
result = alarmDao.createOrUpdateActiveAlarm(request, true);
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isCreated());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertNotEquals(newAlarmId, result.getAlarm().getUuidId());
}
@Test
public void testCantCreateAlarmIfCreateIsDisabled() {
TenantId tenantId = TenantId.fromUUID(UUID.randomUUID());
DeviceId deviceId = new DeviceId(UUID.randomUUID());
AlarmCreateOrUpdateActiveRequest request = AlarmCreateOrUpdateActiveRequest.builder()
.tenantId(tenantId)
.originator(deviceId)
.type("ALARM_TYPE")
.severity(AlarmSeverity.MAJOR)
.build();
AlarmApiCallResult result = alarmDao.createOrUpdateActiveAlarm(request, false);
assertFalse(result.isSuccessful());
}
@Test
public void testAckAlarmProcedure() {
UUID tenantId = UUID.randomUUID();
UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
long ackTs = System.currentTimeMillis();
AlarmApiCallResult result = alarmDao.acknowledgeAlarm(alarm.getTenantId(), alarm.getId(), ackTs);
AlarmInfo afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertEquals(ackTs, result.getAlarm().getAckTs());
assertTrue(result.getAlarm().isAcknowledged());
result = alarmDao.acknowledgeAlarm(alarm.getTenantId(), alarm.getId(), ackTs + 1);
assertNotNull(result);
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertTrue(result.isSuccessful());
assertFalse(result.isModified());
assertEquals(ackTs, result.getAlarm().getAckTs());
assertTrue(result.getAlarm().isAcknowledged());
}
@Test
public void testClearAlarmProcedure() {
UUID tenantId = UUID.randomUUID();
;
UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
long clearTs = System.currentTimeMillis();
AlarmApiCallResult result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs, null);
AlarmInfo afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isCleared());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode());
assertNotNull(result);
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertTrue(result.isSuccessful());
assertFalse(result.isCleared());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
}
@Test
public void testAssignAlarmProcedure() {
UUID tenantId = UUID.randomUUID();
;
UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarmId = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
UserId userId1 = new UserId(UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d7b"));
UserId userId2 = new UserId(UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d8b"));
Alarm alarm = saveAlarm(alarmId, tenantId, originator1Id, "TEST_ALARM");
long assignTs = System.currentTimeMillis();
AlarmApiCallResult result = alarmDao.assignAlarm(alarm.getTenantId(), alarm.getId(), userId1, assignTs);
AlarmInfo afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertEquals(assignTs, result.getAlarm().getAssignTs());
assertNotNull(result.getAlarm().getAssigneeId());
assertEquals(userId1, result.getAlarm().getAssigneeId());
result = alarmDao.assignAlarm(alarm.getTenantId(), alarm.getId(), userId1, assignTs + 1);
afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertTrue(result.isSuccessful());
assertFalse(result.isModified());
assertEquals(assignTs, result.getAlarm().getAssignTs());
assertNotNull(result.getAlarm().getAssigneeId());
assertEquals(userId1, result.getAlarm().getAssigneeId());
result = alarmDao.assignAlarm(alarm.getTenantId(), alarm.getId(), userId2, assignTs + 1);
afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertEquals(assignTs + 1, result.getAlarm().getAssignTs());
assertNotNull(result.getAlarm().getAssigneeId());
assertEquals(userId2, result.getAlarm().getAssigneeId());
result = alarmDao.unassignAlarm(alarm.getTenantId(), alarm.getId(), assignTs + 1);
afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertNull(result.getAlarm().getAssigneeId());
result = alarmDao.unassignAlarm(alarm.getTenantId(), alarm.getId(), assignTs + 1);
afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertFalse(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertNull(result.getAlarm().getAssigneeId());
}
private Alarm saveAlarm(UUID id, UUID tenantId, UUID deviceId, String type) {
Alarm alarm = new Alarm();
alarm.setId(new AlarmId(id));
alarm.setTenantId(TenantId.fromUUID(tenantId));
@ -78,7 +295,10 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
alarm.setPropagate(true);
alarm.setStartTs(System.currentTimeMillis());
alarm.setEndTs(System.currentTimeMillis());
alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
alarmDao.save(TenantId.fromUUID(tenantId), alarm);
alarm.setAcknowledged(false);
alarm.setCleared(false);
alarm.setDetails(JacksonUtil.newObjectNode().put("a", UUID.randomUUID().toString()).set("b", JacksonUtil.newObjectNode().put("a", "[}/.`1321421!@@$$(%&&$")));
return alarmDao.save(TenantId.fromUUID(tenantId), alarm);
}
}

8
rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

@ -415,6 +415,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/clear", null, alarmId.getId());
}
public void assignAlarm(AlarmId alarmId, UserId userId) {
restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/assign/{userId}", null, alarmId.getId(), userId.getId());
}
public void unassignAlarm(AlarmId alarmId) {
restTemplate.delete(baseURL + "/api/alarm/{alarmId}/assign", alarmId.getId());
}
public PageData<AlarmInfo> getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, Boolean fetchOriginator) {
String urlSecondPart = "/api/alarm/{entityType}/{entityId}?fetchOriginator={fetchOriginator}";
Map<String, String> params = new HashMap<>();

49
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java

@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.api;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@ -23,13 +24,17 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
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.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import java.util.Collection;
@ -39,29 +44,65 @@ import java.util.Collection;
*/
public interface RuleEngineAlarmService {
Alarm createOrUpdateAlarm(Alarm alarm);
/*
* New API, since 3.5.
*/
Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId);
/**
* Designed for atomic operations over active alarms.
* Only one active alarm may exist for the pair {originatorId, alarmType}
*/
AlarmApiCallResult createAlarm(AlarmCreateOrUpdateActiveRequest request);
/**
* Designed to update existing alarm. Accepts only part of the alarm fields.
*/
AlarmApiCallResult updateAlarm(AlarmUpdateRequest request);
AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details);
AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs);
AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs);
/*
* Legacy API, before 3.5.
*/
@Deprecated(since = "3.5", forRemoval = true)
Alarm createOrUpdateAlarm(Alarm alarm);
@Deprecated(since = "3.5", forRemoval = true)
ListenableFuture<Boolean> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
@Deprecated(since = "3.5", forRemoval = true)
ListenableFuture<Boolean> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
@Deprecated(since = "3.5", forRemoval = true)
ListenableFuture<AlarmOperationResult> clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
// Other API
Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId);
ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId);
Alarm findAlarmById(TenantId tenantId, AlarmId alarmId);
Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId);
AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId);
default ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) {
return Futures.immediateFuture(findAlarmInfoById(tenantId, alarmId));
}
ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query);
ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query);
AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus);
AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId);
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
}

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmResult.java

@ -17,7 +17,9 @@ package org.thingsboard.rule.engine.action;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.jetbrains.annotations.NotNull;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
@Data
@AllArgsConstructor
@ -34,4 +36,8 @@ public class TbAlarmResult {
this.isCleared = isCleared;
this.alarm = alarm;
}
public static TbAlarmResult fromAlarmResult(AlarmApiCallResult result) {
return new TbAlarmResult(result.isCreated(), result.isModified(), result.isSeverityChanged(), result.isCleared(), result.getAlarm());
}
}

39
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java

@ -26,10 +26,10 @@ import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
@Slf4j
@RuleNode(
@ -57,38 +57,29 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode<TbClearAlarmNodeConfig
@Override
protected ListenableFuture<TbAlarmResult> processAlarm(TbContext ctx, TbMsg msg) {
String alarmType = TbNodeUtils.processPattern(this.config.getAlarmType(), msg);
ListenableFuture<Alarm> alarmFuture;
Alarm alarm;
if (msg.getOriginator().getEntityType().equals(EntityType.ALARM)) {
alarmFuture = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), new AlarmId(msg.getOriginator().getId()));
alarm = ctx.getAlarmService().findAlarmById(ctx.getTenantId(), new AlarmId(msg.getOriginator().getId()));
} else {
alarmFuture = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), alarmType);
alarm = ctx.getAlarmService().findLatestActiveByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), alarmType);
}
return Futures.transformAsync(alarmFuture, a -> {
if (a != null && !a.getStatus().isCleared()) {
return clearAlarm(ctx, msg, a);
}
return Futures.immediateFuture(new TbAlarmResult(false, false, false, null));
}, ctx.getDbCallbackExecutor());
if (alarm != null && !alarm.getStatus().isCleared()) {
return clearAlarm(ctx, msg, alarm);
}
return Futures.immediateFuture(new TbAlarmResult(false, false, false, null));
}
private ListenableFuture<TbAlarmResult> clearAlarm(TbContext ctx, TbMsg msg, Alarm alarm) {
ctx.logJsEvalRequest();
ListenableFuture<JsonNode> asyncDetails = buildAlarmDetails(ctx, msg, alarm.getDetails());
return Futures.transformAsync(asyncDetails, details -> {
return Futures.transform(asyncDetails, details -> {
ctx.logJsEvalResponse();
ListenableFuture<Boolean> clearFuture = ctx.getAlarmService().clearAlarm(ctx.getTenantId(), alarm.getId(), details, System.currentTimeMillis());
return Futures.transformAsync(clearFuture, cleared -> {
ListenableFuture<Alarm> savedAlarmFuture = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), alarm.getId());
return Futures.transformAsync(savedAlarmFuture, savedAlarm -> {
if (cleared && savedAlarm != null) {
alarm.setDetails(savedAlarm.getDetails());
alarm.setEndTs(savedAlarm.getEndTs());
alarm.setClearTs(savedAlarm.getClearTs());
}
alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK);
return Futures.immediateFuture(new TbAlarmResult(false, false, true, alarm));
}, ctx.getDbCallbackExecutor());
}, ctx.getDbCallbackExecutor());
AlarmApiCallResult result = ctx.getAlarmService().clearAlarm(ctx.getTenantId(), alarm.getId(), System.currentTimeMillis(), details);
if (result.isSuccessful()) {
return new TbAlarmResult(false, false, result.isCleared(), result.getAlarm());
} else {
return new TbAlarmResult(false, false, false, alarm);
}
}, ctx.getDbCallbackExecutor());
}
}

49
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java

@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.action;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@ -30,10 +29,12 @@ import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import java.io.IOException;
import java.util.List;
@ -56,16 +57,16 @@ import java.util.List;
)
public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConfiguration> {
private static ObjectMapper mapper = new ObjectMapper();
private static final ObjectMapper mapper = new ObjectMapper();
private List<String> relationTypes;
private AlarmSeverity notDynamicAlarmSeverity;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
super.init(ctx, configuration);
if(!this.config.isDynamicSeverity()) {
if (!this.config.isDynamicSeverity()) {
this.notDynamicAlarmSeverity = EnumUtils.getEnum(AlarmSeverity.class, this.config.getSeverity());
if(this.notDynamicAlarmSeverity == null) {
if (this.notDynamicAlarmSeverity == null) {
throw new TbNodeException("Incorrect Alarm Severity value: " + this.config.getSeverity());
}
}
@ -97,15 +98,12 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
}
}
ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), alarmType);
return Futures.transformAsync(latest, existingAlarm -> {
if (existingAlarm == null || existingAlarm.getStatus().isCleared()) {
return createNewAlarm(ctx, msg, msgAlarm);
} else {
return updateAlarm(ctx, msg, existingAlarm, msgAlarm);
}
}, ctx.getDbCallbackExecutor());
Alarm existingAlarm = ctx.getAlarmService().findLatestActiveByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), alarmType);
if (existingAlarm == null || existingAlarm.getStatus().isCleared()) {
return createNewAlarm(ctx, msg, msgAlarm);
} else {
return updateAlarm(ctx, msg, existingAlarm, msgAlarm);
}
}
private Alarm getAlarmFromMessage(TbContext ctx, TbMsg msg) throws IOException {
@ -115,9 +113,6 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
if (msgAlarm.getOriginator() == null) {
msgAlarm.setOriginator(msg.getOriginator());
}
if (msgAlarm.getStatus() == null) {
msgAlarm.setStatus(AlarmStatus.ACTIVE_UNACK);
}
return msgAlarm;
}
@ -130,7 +125,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
} else {
asyncDetails = Futures.immediateFuture(null);
}
ListenableFuture<Alarm> asyncAlarm = Futures.transform(asyncDetails, details -> {
ListenableFuture<Alarm> asyncAlarm = Futures.transform(asyncDetails, details -> {
if (buildDetails) {
ctx.logJsEvalResponse();
}
@ -145,9 +140,9 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
}
return newAlarm;
}, MoreExecutors.directExecutor());
ListenableFuture<Alarm> asyncCreated = Futures.transform(asyncAlarm,
alarm -> ctx.getAlarmService().createOrUpdateAlarm(alarm), ctx.getDbCallbackExecutor());
return Futures.transform(asyncCreated, alarm -> new TbAlarmResult(true, false, false, alarm), MoreExecutors.directExecutor());
ListenableFuture<AlarmApiCallResult> asyncCreated = Futures.transform(asyncAlarm,
alarm -> ctx.getAlarmService().createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm)), ctx.getDbCallbackExecutor());
return Futures.transform(asyncCreated, TbAlarmResult::fromAlarmResult, MoreExecutors.directExecutor());
}
private ListenableFuture<TbAlarmResult> updateAlarm(TbContext ctx, TbMsg msg, Alarm existingAlarm, Alarm msgAlarm) {
@ -159,7 +154,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
} else {
asyncDetails = Futures.immediateFuture(null);
}
ListenableFuture<Alarm> asyncUpdated = Futures.transform(asyncDetails, (Function<JsonNode, Alarm>) details -> {
ListenableFuture<AlarmApiCallResult> asyncUpdated = Futures.transform(asyncDetails, details -> {
if (buildDetails) {
ctx.logJsEvalResponse();
}
@ -183,10 +178,9 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
existingAlarm.setDetails(details);
}
existingAlarm.setEndTs(System.currentTimeMillis());
return ctx.getAlarmService().createOrUpdateAlarm(existingAlarm);
return ctx.getAlarmService().updateAlarm(AlarmUpdateRequest.fromAlarm(existingAlarm));
}, ctx.getDbCallbackExecutor());
return Futures.transform(asyncUpdated, a -> new TbAlarmResult(false, true, false, a), MoreExecutors.directExecutor());
return Futures.transform(asyncUpdated, TbAlarmResult::fromAlarmResult, MoreExecutors.directExecutor());
}
private Alarm buildAlarm(TbMsg msg, JsonNode details, TenantId tenantId) {
@ -194,7 +188,8 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
return Alarm.builder()
.tenantId(tenantId)
.originator(msg.getOriginator())
.status(AlarmStatus.ACTIVE_UNACK)
.cleared(false)
.acknowledged(false)
.severity(this.config.isDynamicSeverity() ? processAlarmSeverity(msg) : notDynamicAlarmSeverity)
.propagate(config.isPropagate())
.propagateToOwner(config.isPropagateToOwner())
@ -209,7 +204,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
private AlarmSeverity processAlarmSeverity(TbMsg msg) {
AlarmSeverity severity = EnumUtils.getEnum(AlarmSeverity.class, TbNodeUtils.processPattern(this.config.getSeverity(), msg));
if(severity == null) {
if (severity == null) {
throw new RuntimeException("Used incorrect pattern or Alarm Severity not included in message");
}
return severity;

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java

@ -137,7 +137,7 @@ public class TbMsgGeneratorNode implements TbNode {
}
lastScheduledTs = lastScheduledTs + delay;
long curDelay = Math.max(0L, (lastScheduledTs - curTs));
TbMsg tickMsg = ctx.newMsg(null, TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), "");
TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), "");
nextTickId = tickMsg.getId();
ctx.tellSelf(tickMsg, curDelay);
}
@ -145,14 +145,14 @@ public class TbMsgGeneratorNode implements TbNode {
private ListenableFuture<TbMsg> generate(TbContext ctx, TbMsg msg) {
log.trace("generate, config {}", config);
if (prevMsg == null) {
prevMsg = ctx.newMsg(null, "", originatorId, msg.getCustomerId(), new TbMsgMetaData(), "{}");
prevMsg = ctx.newMsg(config.getQueueName(), "", originatorId, msg.getCustomerId(), new TbMsgMetaData(), "{}");
}
if (initialized.get()) {
ctx.logJsEvalRequest();
return Futures.transformAsync(scriptEngine.executeGenerateAsync(prevMsg), generated -> {
log.trace("generate process response, generated {}, config {}", generated, config);
ctx.logJsEvalResponse();
prevMsg = ctx.newMsg(null, generated.getType(), originatorId, msg.getCustomerId(), generated.getMetaData(), generated.getData());
prevMsg = ctx.newMsg(config.getQueueName(), generated.getType(), originatorId, msg.getCustomerId(), generated.getMetaData(), generated.getData());
return Futures.immediateFuture(prevMsg);
}, MoreExecutors.directExecutor()); //usually it runs on js-executor-remote-callback thread pool
}

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java

@ -36,6 +36,7 @@ public class TbMsgGeneratorNodeConfiguration implements NodeConfiguration<TbMsgG
private ScriptLanguage scriptLang;
private String jsScript;
private String tbelScript;
private String queueName;
@Override
public TbMsgGeneratorNodeConfiguration defaultConfiguration() {

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

@ -17,12 +17,10 @@ package org.thingsboard.rule.engine.profile;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.action.TbAlarmResult;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState;
@ -30,16 +28,16 @@ import org.thingsboard.rule.engine.profile.state.PersistedAlarmState;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import java.util.ArrayList;
import java.util.Comparator;
@ -127,16 +125,12 @@ class AlarmState {
for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
stateUpdate = clearAlarmState(stateUpdate, state);
}
ListenableFuture<AlarmOperationResult> alarmClearOperationResult = ctx.getAlarmService().clearAlarmForResult(
ctx.getTenantId(), currentAlarm.getId(), createDetails(clearState), System.currentTimeMillis()
AlarmApiCallResult result = ctx.getAlarmService().clearAlarm(
ctx.getTenantId(), currentAlarm.getId(), System.currentTimeMillis(), createDetails(clearState)
);
DonAsynchron.withCallback(alarmClearOperationResult,
result -> {
pushMsg(ctx, msg, new TbAlarmResult(false, false, true, result.getAlarm()), clearState);
},
throwable -> {
throw new RuntimeException(throwable);
});
if (result.isCleared()) {
pushMsg(ctx, msg, new TbAlarmResult(false, false, true, result.getAlarm()), clearState);
}
currentAlarm = null;
} else if (AlarmEvalResult.FALSE.equals(evalResult)) {
stateUpdate = clearAlarmState(stateUpdate, clearState);
@ -165,9 +159,9 @@ class AlarmState {
return true;
}
public void initCurrentAlarm(TbContext ctx) throws InterruptedException, ExecutionException {
public void initCurrentAlarm(TbContext ctx) {
if (!initialFetchDone) {
Alarm alarm = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), originator, alarmDefinition.getAlarmType()).get();
Alarm alarm = ctx.getAlarmService().findLatestActiveByOriginatorAndType(ctx.getTenantId(), originator, alarmDefinition.getAlarmType());
if (alarm != null && !alarm.getStatus().isCleared()) {
currentAlarm = alarm;
}
@ -241,21 +235,18 @@ class AlarmState {
// Skip update if severity is decreased.
if (severity.ordinal() <= oldSeverity.ordinal()) {
currentAlarm.setDetails(createDetails(ruleState));
if (!oldSeverity.equals(severity)) {
currentAlarm.setSeverity(severity);
currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm);
return new TbAlarmResult(false, false, true, false, currentAlarm);
} else {
currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm);
return new TbAlarmResult(false, true, false, false, currentAlarm);
}
currentAlarm.setSeverity(severity);
AlarmApiCallResult result = ctx.getAlarmService().updateAlarm(AlarmUpdateRequest.fromAlarm(currentAlarm));
currentAlarm = result.getAlarm();
return TbAlarmResult.fromAlarmResult(result);
} else {
return null;
}
} else {
currentAlarm = new Alarm();
currentAlarm.setType(alarmDefinition.getAlarmType());
currentAlarm.setStatus(AlarmStatus.ACTIVE_UNACK);
currentAlarm.setAcknowledged(false);
currentAlarm.setCleared(false);
currentAlarm.setSeverity(severity);
long startTs = dataSnapshot.getTs();
if (startTs == 0L) {
@ -272,9 +263,9 @@ class AlarmState {
if (alarmDefinition.getPropagateRelationTypes() != null) {
currentAlarm.setPropagateRelationTypes(alarmDefinition.getPropagateRelationTypes());
}
currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm);
boolean updated = currentAlarm.getStartTs() != currentAlarm.getEndTs();
return new TbAlarmResult(!updated, updated, false, false, currentAlarm);
AlarmApiCallResult result = ctx.getAlarmService().createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(currentAlarm));
currentAlarm = result.getAlarm();
return TbAlarmResult.fromAlarmResult(result);
}
}
@ -342,7 +333,7 @@ class AlarmState {
public void processAckAlarm(Alarm alarm) {
if (currentAlarm != null && currentAlarm.getId().equals(alarm.getId())) {
currentAlarm.setStatus(alarm.getStatus());
currentAlarm.setAcknowledged(alarm.isAcknowledged());
currentAlarm.setAckTs(alarm.getAckTs());
}
}

305
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java

@ -28,7 +28,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
import org.thingsboard.rule.engine.api.ScriptEngine;
@ -36,6 +35,9 @@ import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
@ -46,6 +48,7 @@ import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import javax.script.ScriptException;
import java.io.IOException;
@ -59,7 +62,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.times;
@ -71,9 +73,6 @@ import static org.thingsboard.server.common.data.DataConstants.IS_EXISTING_ALARM
import static org.thingsboard.server.common.data.DataConstants.IS_NEW_ALARM;
import static org.thingsboard.server.common.data.alarm.AlarmSeverity.CRITICAL;
import static org.thingsboard.server.common.data.alarm.AlarmSeverity.WARNING;
import static org.thingsboard.server.common.data.alarm.AlarmStatus.ACTIVE_UNACK;
import static org.thingsboard.server.common.data.alarm.AlarmStatus.CLEARED_ACK;
import static org.thingsboard.server.common.data.alarm.AlarmStatus.CLEARED_UNACK;
@RunWith(MockitoJUnitRunner.class)
public class TbAlarmNodeTest {
@ -93,16 +92,16 @@ public class TbAlarmNodeTest {
@Captor
private ArgumentCaptor<Consumer<Throwable>> failureCaptor;
private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased());
private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased());
private final RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased());
private final RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased());
private ListeningExecutor dbExecutor;
private EntityId originator = new DeviceId(Uuids.timeBased());
private EntityId alarmOriginator = new AlarmId(Uuids.timeBased());
private TenantId tenantId = TenantId.fromUUID(Uuids.timeBased());
private TbMsgMetaData metaData = new TbMsgMetaData();
private String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
private final EntityId originator = new DeviceId(Uuids.timeBased());
private final EntityId alarmOriginator = new AlarmId(Uuids.timeBased());
private final TenantId tenantId = TenantId.fromUUID(Uuids.timeBased());
private final TbMsgMetaData metaData = new TbMsgMetaData();
private final String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
@Before
public void before() {
@ -128,11 +127,26 @@ public class TbAlarmNodeTest {
initWithCreateAlarmScript();
metaData.putValue("key", "value");
TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
long ts = msg.getTs();
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null));
doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class));
long ts = msg.getTs();
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(null);
Alarm expectedAlarm = Alarm.builder()
.startTs(ts)
.endTs(ts)
.tenantId(tenantId)
.originator(originator)
.severity(CRITICAL)
.propagate(true)
.type("SomeType")
.details(null)
.build();
when(alarmService.createAlarm(any(AlarmCreateOrUpdateActiveRequest.class))).thenReturn(
AlarmApiCallResult.builder()
.created(true)
.alarm(new AlarmInfo(expectedAlarm))
.build());
node.onMsg(ctx, msg);
verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture());
@ -153,18 +167,6 @@ public class TbAlarmNodeTest {
assertNotSame(metaData, metadataCaptor.getValue());
Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
Alarm expectedAlarm = Alarm.builder()
.startTs(ts)
.endTs(ts)
.tenantId(tenantId)
.originator(originator)
.status(ACTIVE_UNACK)
.severity(CRITICAL)
.propagate(true)
.type("SomeType")
.details(null)
.build();
assertEquals(expectedAlarm, actualAlarm);
}
@ -175,7 +177,7 @@ public class TbAlarmNodeTest {
TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFailedFuture(new NotImplementedException("message")));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(null);
node.onMsg(ctx, msg);
@ -183,10 +185,10 @@ public class TbAlarmNodeTest {
verify(ctx).createScriptEngine(ScriptLanguage.JS, "DETAILS");
verify(ctx).getAlarmService();
verify(ctx, times(3)).getDbCallbackExecutor();
verify(ctx, times(2)).getDbCallbackExecutor();
verify(ctx).logJsEvalRequest();
verify(ctx).getTenantId();
verify(alarmService).findLatestByOriginatorAndType(tenantId, originator, "SomeType");
verify(alarmService).findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType");
verifyNoMoreInteractions(ctx, alarmService);
}
@ -197,12 +199,27 @@ public class TbAlarmNodeTest {
metaData.putValue("key", "value");
TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
long ts = msg.getTs();
Alarm clearedAlarm = Alarm.builder().status(CLEARED_ACK).build();
Alarm clearedAlarm = Alarm.builder().cleared(true).acknowledged(true).build();
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(clearedAlarm));
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(clearedAlarm);
doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class));
Alarm expectedAlarm = Alarm.builder()
.startTs(ts)
.endTs(ts)
.tenantId(tenantId)
.originator(originator)
.severity(CRITICAL)
.propagate(true)
.type("SomeType")
.details(null)
.build();
when(alarmService.createAlarm(any(AlarmCreateOrUpdateActiveRequest.class))).thenReturn(
AlarmApiCallResult.builder()
.successful(true)
.created(true)
.alarm(new AlarmInfo(expectedAlarm))
.build());
node.onMsg(ctx, msg);
@ -225,35 +242,36 @@ public class TbAlarmNodeTest {
Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
Alarm expectedAlarm = Alarm.builder()
.startTs(ts)
.endTs(ts)
.tenantId(tenantId)
.originator(originator)
.status(ACTIVE_UNACK)
.severity(CRITICAL)
.propagate(true)
.type("SomeType")
.details(null)
.build();
assertEquals(expectedAlarm, actualAlarm);
}
@Test
public void alarmCanBeUpdated() throws ScriptException, IOException {
public void alarmCanBeUpdated() throws IOException {
initWithCreateAlarmScript();
metaData.putValue("key", "value");
TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
long oldEndDate = System.currentTimeMillis();
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build();
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(WARNING).endTs(oldEndDate).build();
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(activeAlarm));
doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(activeAlarm);
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(activeAlarm);
Alarm expectedAlarm = Alarm.builder()
.tenantId(tenantId)
.originator(originator)
.severity(CRITICAL)
.propagate(true)
.type("SomeType")
.details(null)
.endTs(activeAlarm.getEndTs())
.build();
when(alarmService.updateAlarm(any(AlarmUpdateRequest.class))).thenReturn(
AlarmApiCallResult.builder()
.successful(true)
.modified(true)
.alarm(new AlarmInfo(expectedAlarm))
.build());
node.onMsg(ctx, msg);
verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture());
@ -274,18 +292,7 @@ public class TbAlarmNodeTest {
assertNotSame(metaData, metadataCaptor.getValue());
Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
assertTrue(activeAlarm.getEndTs() > oldEndDate);
Alarm expectedAlarm = Alarm.builder()
.tenantId(tenantId)
.originator(originator)
.status(ACTIVE_UNACK)
.severity(CRITICAL)
.propagate(true)
.type("SomeType")
.details(null)
.endTs(activeAlarm.getEndTs())
.build();
assertTrue(activeAlarm.getEndTs() >= oldEndDate);
assertEquals(expectedAlarm, actualAlarm);
}
@ -293,17 +300,30 @@ public class TbAlarmNodeTest {
public void alarmCanBeCleared() throws ScriptException, IOException {
initWithClearAlarmScript();
metaData.putValue("key", "value");
TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
long oldEndDate = System.currentTimeMillis();
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build();
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(WARNING).endTs(oldEndDate).build();
Alarm expectedAlarm = Alarm.builder()
.tenantId(tenantId)
.originator(originator)
.cleared(true)
.severity(WARNING)
.propagate(false)
.type("SomeType")
.details(null)
.endTs(oldEndDate)
.build();
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(activeAlarm));
when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), nullable(JsonNode.class), anyLong()))
.thenReturn(Futures.immediateFuture( false));
when(alarmService.findAlarmByIdAsync(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()))).thenReturn(Futures.immediateFuture(activeAlarm));
// doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(activeAlarm);
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(activeAlarm);
when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), anyLong(), nullable(JsonNode.class)))
.thenReturn(AlarmApiCallResult.builder()
.successful(true)
.cleared(true)
.alarm(new AlarmInfo(expectedAlarm))
.build());
node.onMsg(ctx, msg);
@ -325,17 +345,6 @@ public class TbAlarmNodeTest {
assertNotSame(metaData, metadataCaptor.getValue());
Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
Alarm expectedAlarm = Alarm.builder()
.tenantId(tenantId)
.originator(originator)
.status(CLEARED_UNACK)
.severity(WARNING)
.propagate(false)
.type("SomeType")
.details(null)
.endTs(oldEndDate)
.build();
assertEquals(expectedAlarm, actualAlarm);
}
@ -343,17 +352,33 @@ public class TbAlarmNodeTest {
public void alarmCanBeClearedWithAlarmOriginator() throws ScriptException, IOException {
initWithClearAlarmScript();
metaData.putValue("key", "value");
TbMsg msg = TbMsg.newMsg( "USER", alarmOriginator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
TbMsg msg = TbMsg.newMsg("USER", alarmOriginator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
long oldEndDate = System.currentTimeMillis();
AlarmId id = new AlarmId(alarmOriginator.getId());
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build();
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(WARNING).endTs(oldEndDate).build();
activeAlarm.setId(id);
Alarm expectedAlarm = Alarm.builder()
.tenantId(tenantId)
.originator(originator)
.cleared(true)
.severity(WARNING)
.propagate(false)
.type("SomeType")
.details(null)
.endTs(oldEndDate)
.build();
expectedAlarm.setId(id);
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findAlarmByIdAsync(tenantId, id)).thenReturn(Futures.immediateFuture(activeAlarm));
when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), nullable(JsonNode.class), anyLong())).thenReturn(Futures.immediateFuture(true));
// doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(activeAlarm);
when(alarmService.findAlarmById(tenantId, id)).thenReturn(activeAlarm);
when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), anyLong(), nullable(JsonNode.class)))
.thenReturn(AlarmApiCallResult.builder()
.successful(true)
.cleared(true)
.alarm(new AlarmInfo(expectedAlarm))
.build());
node.onMsg(ctx, msg);
@ -375,18 +400,6 @@ public class TbAlarmNodeTest {
assertNotSame(metaData, metadataCaptor.getValue());
Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
Alarm expectedAlarm = Alarm.builder()
.tenantId(tenantId)
.originator(originator)
.status(CLEARED_UNACK)
.severity(WARNING)
.propagate(false)
.type("SomeType")
.details(null)
.endTs(oldEndDate)
.build();
expectedAlarm.setId(id);
assertEquals(expectedAlarm, actualAlarm);
}
@ -415,9 +428,25 @@ public class TbAlarmNodeTest {
metaData.putValue("key", "value");
TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
long ts = msg.getTs();
Alarm expectedAlarm = Alarm.builder()
.startTs(ts)
.endTs(ts)
.tenantId(tenantId)
.originator(originator)
.severity(WARNING)
.propagate(true)
.type("SomeType")
.details(null)
.build();
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null));
doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class));
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(null);
when(alarmService.createAlarm(any(AlarmCreateOrUpdateActiveRequest.class))).thenReturn(
AlarmApiCallResult.builder()
.successful(true)
.created(true)
.alarm(new AlarmInfo(expectedAlarm))
.build());
node.onMsg(ctx, msg);
@ -439,18 +468,6 @@ public class TbAlarmNodeTest {
assertNotSame(metaData, metadataCaptor.getValue());
Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
Alarm expectedAlarm = Alarm.builder()
.startTs(ts)
.endTs(ts)
.tenantId(tenantId)
.originator(originator)
.status(ACTIVE_UNACK)
.severity(WARNING)
.propagate(true)
.type("SomeType")
.details(null)
.build();
assertEquals(expectedAlarm, actualAlarm);
}
@ -478,10 +495,25 @@ public class TbAlarmNodeTest {
metaData.putValue("alarmSeverity", "WARNING");
TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
long ts = msg.getTs();
Alarm expectedAlarm = Alarm.builder()
.startTs(ts)
.endTs(ts)
.tenantId(tenantId)
.originator(originator)
.severity(WARNING)
.propagate(true)
.type("SomeType")
.details(null)
.build();
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null));
doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class));
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(null);
when(alarmService.createAlarm(any(AlarmCreateOrUpdateActiveRequest.class))).thenReturn(
AlarmApiCallResult.builder()
.successful(true)
.created(true)
.alarm(new AlarmInfo(expectedAlarm))
.build());
node.onMsg(ctx, msg);
@ -502,23 +534,11 @@ public class TbAlarmNodeTest {
assertNotSame(metaData, metadataCaptor.getValue());
Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
Alarm expectedAlarm = Alarm.builder()
.startTs(ts)
.endTs(ts)
.tenantId(tenantId)
.originator(originator)
.status(ACTIVE_UNACK)
.severity(WARNING)
.propagate(true)
.type("SomeType")
.details(null)
.build();
assertEquals(expectedAlarm, actualAlarm);
}
@Test
public void testCreateAlarmsWithPropagationToTenantWithDynamicTypes() throws Exception{
public void testCreateAlarmsWithPropagationToTenantWithDynamicTypes() throws Exception {
for (int i = 0; i < 10; i++) {
var config = new TbCreateAlarmNodeConfiguration();
config.setPropagateToTenant(true);
@ -541,11 +561,26 @@ public class TbAlarmNodeTest {
metaData.putValue("key", "value");
TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
long ts = msg.getTs();
Alarm expectedAlarm = Alarm.builder()
.startTs(ts)
.endTs(ts)
.tenantId(tenantId)
.originator(originator)
.severity(CRITICAL)
.propagateToTenant(true)
.type("SomeType" + i)
.details(null)
.build();
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType" + i)).thenReturn(Futures.immediateFuture(null));
doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class));
long ts = msg.getTs();
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType" + i)).thenReturn(null);
when(alarmService.createAlarm(any(AlarmCreateOrUpdateActiveRequest.class))).thenReturn(
AlarmApiCallResult.builder()
.successful(true)
.created(true)
.alarm(new AlarmInfo(expectedAlarm))
.build());
node.onMsg(ctx, msg);
verify(ctx, atMost(10)).enqueue(any(), successCaptor.capture(), failureCaptor.capture());
@ -566,18 +601,6 @@ public class TbAlarmNodeTest {
assertNotSame(metaData, metadataCaptor.getValue());
Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
Alarm expectedAlarm = Alarm.builder()
.startTs(ts)
.endTs(ts)
.tenantId(tenantId)
.originator(originator)
.status(ACTIVE_UNACK)
.severity(CRITICAL)
.propagateToTenant(true)
.type("SomeType" + i)
.details(null)
.build();
assertEquals(expectedAlarm, actualAlarm);
}
}

17
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java

@ -25,6 +25,8 @@ import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
@ -42,6 +44,7 @@ import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.DeviceService;
@ -77,11 +80,15 @@ public class DeviceStateTest {
when(ctx.getAttributesService()).thenReturn(attributesService);
RuleEngineAlarmService alarmService = mock(RuleEngineAlarmService.class);
when(alarmService.findLatestByOriginatorAndType(any(), any(), any())).thenReturn(Futures.immediateFuture(null));
when(alarmService.createOrUpdateAlarm(any())).thenAnswer(invocationOnMock -> {
Alarm alarm = invocationOnMock.getArgument(0);
alarm.setId(new AlarmId(UUID.randomUUID()));
return alarm;
when(alarmService.findLatestActiveByOriginatorAndType(any(), any(), any())).thenReturn(null);
when(alarmService.createAlarm(any())).thenAnswer(invocationOnMock -> {
AlarmCreateOrUpdateActiveRequest request = invocationOnMock.getArgument(0);
return AlarmApiCallResult.builder()
.successful(true)
.created(true)
.modified(true)
.alarm(new AlarmInfo(new Alarm(new AlarmId(UUID.randomUUID()))))
.build();
});
when(ctx.getAlarmService()).thenReturn(alarmService);

122
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java

@ -21,7 +21,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalAnswers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
@ -34,6 +33,8 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
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.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
@ -46,6 +47,7 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -62,6 +64,7 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey;
@ -79,8 +82,10 @@ import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class TbDeviceProfileNodeTest {
@ -187,8 +192,8 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")).thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")).thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())).thenReturn(theMsg);
@ -205,6 +210,8 @@ public class TbDeviceProfileNodeTest {
TbMsg theMsg2 = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "2");
Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())).thenReturn(theMsg2);
registerCreateAlarmMock(alarmService.updateAlarm(any()), false);
TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
@ -274,9 +281,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "alarmEnabledAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "alarmEnabledAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(attrListListenableFuture);
@ -357,9 +364,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "alarmEnabledAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "alarmEnabledAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
@ -430,9 +437,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFutureWithLess);
@ -524,9 +531,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFuture);
@ -642,9 +649,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString()))
.thenReturn(optionalDurationAttribute);
@ -757,9 +764,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFuture);
@ -867,9 +874,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString()))
.thenReturn(optionalDurationAttribute);
@ -969,9 +976,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFuture);
@ -1067,9 +1074,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFuture);
@ -1149,9 +1156,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFutureActiveSchedule);
@ -1246,8 +1253,8 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(null);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFutureInactiveSchedule);
@ -1319,9 +1326,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFutureWithLess);
@ -1394,10 +1401,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any()))
.thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFutureWithLess);
@ -1474,10 +1480,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any()))
.thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId))
.thenReturn(device);
@ -1485,7 +1490,7 @@ public class TbDeviceProfileNodeTest {
.thenReturn(listListenableFutureWithLess);
Mockito.when(attributesService.find(eq(tenantId), eq(customerId), Mockito.anyString(), Mockito.anyString()))
.thenReturn(emptyOptionalFuture);
Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString()))
Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString()))
.thenReturn(optionalListenableFutureWithLess);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
@ -1560,10 +1565,9 @@ public class TbDeviceProfileNodeTest {
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "greaterTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any()))
.thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "greaterTemperatureAlarm"))
.thenReturn(null);
registerCreateAlarmMock(alarmService.createAlarm(any()), true);
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId))
.thenReturn(device);
@ -1571,7 +1575,7 @@ public class TbDeviceProfileNodeTest {
.thenReturn(listListenableFutureWithLess);
Mockito.when(attributesService.find(eq(tenantId), eq(customerId), Mockito.anyString(), Mockito.anyString()))
.thenReturn(emptyOptionalFuture);
Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString()))
Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString()))
.thenReturn(optionalListenableFutureWithLess);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
@ -1601,4 +1605,18 @@ public class TbDeviceProfileNodeTest {
node.init(ctx, nodeConfiguration);
}
private void registerCreateAlarmMock(AlarmApiCallResult a, boolean created) {
when(a).thenAnswer(invocationOnMock -> {
// AlarmCreateOrUpdateActiveRequest request = invocationOnMock.getArgument(0);
AlarmInfo alarm = new AlarmInfo(new Alarm(new AlarmId(UUID.randomUUID())));
alarm.setSeverity(AlarmSeverity.CRITICAL);
return AlarmApiCallResult.builder()
.successful(true)
.created(created)
.modified(true)
.alarm(alarm)
.build();
});
}
}

46
ui-ngx/src/app/core/http/alarm-comment.service.ts

@ -0,0 +1,46 @@
///
/// 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.
///
import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
import { AlarmComment, AlarmCommentInfo } from '@shared/models/alarm.models';
@Injectable({
providedIn: 'root'
})
export class AlarmCommentService {
constructor(
private http: HttpClient
) { }
public saveAlarmComment(alarmId: string, alarmComment: AlarmComment, config?: RequestConfig): Observable<AlarmComment> {
return this.http.post<AlarmComment>(`/api/alarm/${alarmId}/comment`, alarmComment, defaultHttpOptionsFromConfig(config));
}
public getAlarmComments(alarmId: string, pageLink: PageLink, config?: RequestConfig): Observable<PageData<AlarmCommentInfo>> {
return this.http.get<PageData<AlarmComment>>(`/api/alarm/${alarmId}/comment${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
}
public deleteAlarmComments(alarmId: string, commentId: string, config?: RequestConfig): Observable<void> {
return this.http.delete<void>(`/api/alarm/${alarmId}/comment/${commentId}`, defaultHttpOptionsFromConfig(config));
}
}

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

Loading…
Cancel
Save