Browse Source

Merge branch 'develop/3.5' of github.com:thingsboard/thingsboard into develop/3.5

pull/8203/head
Igor Kulikov 3 years ago
parent
commit
b81612e249
  1. 10
      application/src/main/data/json/demo/dashboards/thermostats.json
  2. 2
      application/src/main/data/json/system/widget_bundles/alarm_widgets.json
  3. 320
      application/src/main/data/upgrade/3.4.4/schema_update.sql
  4. 101
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  5. 3
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  6. 2
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  7. 35
      application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
  8. 6
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  9. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
  10. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
  11. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  12. 157
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  13. 15
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
  14. 2
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  15. 7
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  16. 47
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  17. 6
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  18. 27
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  19. 10
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  20. 125
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  21. 23
      application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java
  22. 1
      application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
  23. 4
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  24. 2
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
  25. 204
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
  26. 1
      application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java
  27. 30
      application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
  28. 85
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java
  29. 21
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
  30. 60
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  31. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
  32. 2
      common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java
  33. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
  34. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  35. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  36. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  37. 22
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  38. 22
      common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java
  39. 22
      common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java
  40. 22
      common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java
  41. 2
      common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java
  42. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
  43. 55
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  44. 37
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java
  45. 30
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssigneeUpdate.java
  46. 87
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java
  47. 56
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java
  48. 34
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.java
  49. 46
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java
  50. 3
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
  51. 24
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSearchStatus.java
  52. 118
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatusFilter.java
  53. 79
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java
  54. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java
  55. 3
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  56. 2
      common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
  57. 3
      common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java
  58. 2
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java
  59. 8
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  60. 27
      common/data/src/main/java/org/thingsboard/server/common/data/id/NameLabelAndCustomerDetails.java
  61. 18
      common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java
  62. 11
      common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataPageLink.java
  63. 3
      common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java
  64. 3
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java
  65. 25
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  66. 261
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  67. 2
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  68. 82
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  69. 16
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  70. 45
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java
  71. 49
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java
  72. 2
      dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java
  73. 105
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  74. 244
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  75. 28
      dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java
  76. 125
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
  77. 10
      dao/src/main/resources/sql/schema-entities-idx.sql
  78. 258
      dao/src/main/resources/sql/schema-entities.sql
  79. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java
  80. 320
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java
  81. 1
      dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java
  82. 244
      dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java
  83. 3
      msa/black-box-tests/pom.xml
  84. 27
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestListener.java
  85. 46
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java
  86. 5
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/LoginPageElements.java
  87. 3
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/LoginPageHelper.java
  88. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/AssetProfileEditMenuTest.java
  89. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/CreateAssetProfileImportTest.java
  90. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/CreateAssetProfileTest.java
  91. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/DeleteAssetProfileTest.java
  92. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/DeleteSeveralAssetProfilesTest.java
  93. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/MakeAssetProfileDefaultTest.java
  94. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/SearchAssetProfileTest.java
  95. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/SortByNameTest.java
  96. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/CreateCustomerTest.java
  97. 14
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/CustomerEditMenuTest.java
  98. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/DeleteCustomerTest.java
  99. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/DeleteSeveralCustomerTest.java
  100. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/ManageCustomersAssetsTest.java

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}"
}
}
]

320
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,
@ -31,11 +95,255 @@ CREATE TABLE IF NOT EXISTS user_settings (
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE
);
ALTER TABLE user_credentials
ADD COLUMN IF NOT EXISTS additional_info varchar NOT NULL DEFAULT '{}';
-- ALARM COMMENTS END
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';
-- 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
$$;
UPDATE tb_user SET additional_info = tb_user.additional_info::jsonb - 'userPasswordHistory' WHERE additional_info::jsonb ? 'userPasswordHistory';
-- 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);
}

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

@ -613,7 +613,6 @@ public abstract class BaseController {
throw handleException(e, false);
}
}
Device checkDeviceId(DeviceId deviceId, Operation operation) throws ThingsboardException {
try {
validateId(deviceId, "Incorrect deviceId " + deviceId);
@ -725,7 +724,7 @@ public abstract class BaseController {
AlarmInfo checkAlarmInfoId(AlarmId alarmId, Operation operation) throws ThingsboardException {
try {
validateId(alarmId, "Incorrect alarmId " + alarmId);
AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(getCurrentUser().getTenantId(), alarmId).get();
AlarmInfo alarmInfo = alarmService.findAlarmInfoById(getCurrentUser().getTenantId(), alarmId);
checkNotNull(alarmInfo, "Alarm with id [" + alarmId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarmInfo);
return alarmInfo;

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

@ -86,6 +86,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

@ -76,7 +76,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:

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

@ -15,22 +15,25 @@
*/
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 org.springframework.stereotype.Service;
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.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
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;
@ -44,9 +47,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;
@ -54,43 +82,110 @@ 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();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
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 AlarmInfo clear(Alarm alarm, User user) throws ThingsboardException {
return clear(alarm, System.currentTimeMillis(), user);
}
@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, 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()))
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString())
.put("subtype", "CLEAR"))
.build();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
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();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
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();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
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
@ -101,4 +196,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

@ -26,6 +26,7 @@ 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.TenantId;
import org.thingsboard.server.common.data.rpc.RpcError;
@ -35,6 +36,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@ -502,13 +504,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 {
throwNotHandled(msg, callback);
}

47
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;
@ -40,6 +41,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;
@ -268,7 +270,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);
}
}
@ -292,7 +294,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())) {
@ -301,15 +303,14 @@ 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
);
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())) {
@ -319,8 +320,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
},
s -> alarm.getCreatedTime() >= s.getTs(),
s -> alarm,
true
alarm, true
);
callback.onSuccess();
}
@ -354,7 +354,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
deleteDeviceInactivityTimeout(tenantId, entityId, keys);
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(tenantId,
new DeviceId(entityId.getId()), scope, keys), null);
new DeviceId(entityId.getId()), scope, keys), null);
}
}
callback.onSuccess();
@ -414,19 +414,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 {
@ -557,12 +558,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());
@ -571,8 +572,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,11 +17,13 @@ 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.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 java.util.List;
@ -42,9 +44,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);
}

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;
@ -189,8 +191,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());
}
}
@ -316,7 +318,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());
@ -329,7 +331,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());

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

@ -29,14 +29,18 @@ 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.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.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;
@ -44,6 +48,7 @@ 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.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService;
@ -92,6 +97,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());
@ -115,29 +155,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
@ -151,8 +191,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
@ -166,8 +206,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
@ -175,15 +215,41 @@ 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();
TenantId tenantId = alarm.getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, new AlarmInfo(alarm), TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, new AlarmInfo(alarm));
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
}
});
}
private void onAlarmUpdated(AlarmApiCallResult result) {
wsCallBackExecutor.submit(() -> {
AlarmInfo alarm = result.getAlarm();
TenantId tenantId = alarm.getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
@ -200,10 +266,10 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
});
}
private void onAlarmDeleted(AlarmOperationResult result) {
private void onAlarmDeleted(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()) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
@ -220,9 +286,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);
}
@ -232,4 +298,27 @@ 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());
}
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment.build());
}
}
return result;
}
}

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

@ -17,15 +17,8 @@ package org.thingsboard.server.service.telemetry.sub;
import lombok.Getter;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.query.AlarmData;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
public class AlarmSubscriptionUpdate {
@ -36,15 +29,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;
@ -64,7 +57,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 + "]";
}
}
}

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;

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

@ -167,6 +167,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected TenantId differentTenantId;
protected CustomerId differentCustomerId;
protected UserId customerUserId;
protected UserId differentCustomerUserId;
@SuppressWarnings("rawtypes")
private HttpMessageConverter mappingJackson2HttpMessageConverter;
@ -363,7 +364,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 ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT";
public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT";

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;

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

@ -19,5 +19,25 @@ 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;
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
}

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
);
}
}

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())) {

16
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 ALIAS_PROPERTY = "alias";
@ -287,24 +288,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;

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 ");

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);

258
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,
@ -796,4 +792,246 @@ CREATE TABLE IF NOT EXISTS user_settings (
user_id uuid NOT NULL CONSTRAINT user_settings_pkey PRIMARY KEY,
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);
}
}

3
msa/black-box-tests/pom.xml

@ -212,8 +212,7 @@
<version>${allure-maven.version}</version>
<configuration>
<reportVersion>${allure-testng.version}</reportVersion>
<allureDownloadUrl>https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/
${allure-testng.version}/allure-commandline-${allure-testng.version}.zip</allureDownloadUrl>
<propertiesFilePath>src/test/resources/allure.properties</propertiesFilePath>
</configuration>
</plugin>
</plugins>

27
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestListener.java

@ -19,11 +19,6 @@ import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.WebDriver;
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.internal.ConstructorOrMethod;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import static org.testng.internal.Utils.log;
import static org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest.captureScreen;
@Slf4j
public class TestListener implements ITestListener {
@ -41,13 +36,7 @@ public class TestListener implements ITestListener {
@Override
public void onTestSuccess(ITestResult result) {
log.info("<<<=== Test completed successfully: " + result.getName());
ConstructorOrMethod consOrMethod = result.getMethod().getConstructorOrMethod();
DisableUIListeners disable = consOrMethod.getMethod().getDeclaringClass().getAnnotation(DisableUIListeners.class);
if (disable != null) {
return;
}
driver = ((AbstractDriverBaseTest) result.getInstance()).getDriver();
captureScreen(driver, "success");
}
/**
@ -56,13 +45,6 @@ public class TestListener implements ITestListener {
@Override
public void onTestFailure(ITestResult result) {
log.info("<<<=== Test failed: " + result.getName());
ConstructorOrMethod consOrMethod = result.getMethod().getConstructorOrMethod();
DisableUIListeners disable = consOrMethod.getMethod().getDeclaringClass().getAnnotation(DisableUIListeners.class);
if (disable != null) {
return;
}
driver = ((AbstractDriverBaseTest) result.getInstance()).getDriver();
captureScreen(driver, "failure");
}
/**
@ -71,12 +53,5 @@ public class TestListener implements ITestListener {
@Override
public void onTestSkipped(ITestResult result) {
log.info("<<<=== Test skipped: " + result.getName());
ConstructorOrMethod consOrMethod = result.getMethod().getConstructorOrMethod();
DisableUIListeners disable = consOrMethod.getMethod().getDeclaringClass().getAnnotation(DisableUIListeners.class);
if (disable != null) {
return;
}
driver = ((AbstractDriverBaseTest) result.getInstance()).getDriver();
captureScreen(driver, "skipped");
}
}

46
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java

@ -15,12 +15,10 @@
*/
package org.thingsboard.server.msa.ui.base;
import com.google.common.io.Files;
import io.github.bonigarcia.wdm.WebDriverManager;
import io.qameta.allure.Attachment;
import lombok.SneakyThrows;
import io.qameta.allure.Allure;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
@ -34,7 +32,9 @@ import org.openqa.selenium.remote.LocalFileDetector;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DeviceProfile;
@ -44,9 +44,11 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.ContainerTestSuite;
import java.io.File;
import java.io.ByteArrayInputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.thingsboard.server.msa.TestProperties.getBaseUiUrl;
@ -65,9 +67,8 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
private static final ContainerTestSuite instance = ContainerTestSuite.getInstance();
private JavascriptExecutor js;
@SneakyThrows
@BeforeMethod
public void openBrowser() {
@BeforeClass
public void startUp() throws MalformedURLException {
log.info("===>>> Setup driver");
testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD);
ChromeOptions options = new ChromeOptions();
@ -84,8 +85,18 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
openLocalhost();
}
@BeforeMethod
public void open() {
openHomePage();
}
@AfterMethod
public void closeBrowser() {
public void addScreenshotToReport() {
captureScreen(driver, "After test page screenshot");
}
@AfterClass
public void teardown() {
log.info("<<<=== Teardown");
driver.quit();
}
@ -94,6 +105,10 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
driver.get(getBaseUiUrl());
}
public void openHomePage() {
driver.get(getBaseUiUrl() + "/home");
}
public String getUrl() {
return driver.getCurrentUrl();
}
@ -157,11 +172,10 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
}
}
@SneakyThrows
@Attachment(value = "Page screenshot", type = "image/png")
public static byte[] captureScreen(WebDriver driver, String dirPath) {
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(screenshot, new File("./target/allure-results/screenshots/" + dirPath + "//" + screenshot.getName()));
return Files.toByteArray(screenshot);
public void captureScreen(WebDriver driver, String screenshotName) {
if (driver instanceof TakesScreenshot) {
Allure.addAttachment(screenshotName,
new ByteArrayInputStream(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES)));
}
}
}
}

5
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/LoginPageElements.java

@ -27,6 +27,7 @@ public class LoginPageElements extends AbstractBasePage {
private static final String EMAIL_FIELD = "//input[@id='username-input']";
private static final String PASSWORD_FIELD = "//input[@id='password-input']";
private static final String SUBMIT_BTN = "//button[@type='submit']";
private static final String TITLE_LOGO = "//img[@class='tb-logo-title']";
public WebElement emailField() {
return waitUntilElementToBeClickable(EMAIL_FIELD);
@ -40,4 +41,8 @@ public class LoginPageElements extends AbstractBasePage {
return waitUntilElementToBeClickable(SUBMIT_BTN);
}
public WebElement titleLogo() {
return waitUntilVisibilityOfElementLocated(TITLE_LOGO);
}
}

3
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/LoginPageHelper.java

@ -16,6 +16,8 @@
package org.thingsboard.server.msa.ui.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.thingsboard.server.msa.ui.utils.Const;
public class LoginPageHelper extends LoginPageElements {
@ -27,5 +29,6 @@ public class LoginPageHelper extends LoginPageElements {
emailField().sendKeys(Const.TENANT_EMAIL);
passwordField().sendKeys(Const.TENANT_PASSWORD);
submitBtn().click();
waitUntilUrlContainsText("/home");
}
}

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/AssetProfileEditMenuTest.java

@ -19,7 +19,7 @@ import io.qameta.allure.Description;
import org.openqa.selenium.Keys;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
@ -39,7 +39,7 @@ public class AssetProfileEditMenuTest extends AbstractDriverBaseTest {
private ProfilesPageHelper profilesPage;
private String name;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver);

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/CreateAssetProfileImportTest.java

@ -18,7 +18,7 @@ package org.thingsboard.server.msa.ui.tests.assetProfileSmoke;
import io.qameta.allure.Description;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
@ -40,7 +40,7 @@ public class CreateAssetProfileImportTest extends AbstractDriverBaseTest {
private final String absolutePathToFileImportTxt = getClass().getClassLoader().getResource(IMPORT_TXT_FILE_NAME).getPath();
private String name;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver);

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/CreateAssetProfileTest.java

@ -18,7 +18,7 @@ package org.thingsboard.server.msa.ui.tests.assetProfileSmoke;
import io.qameta.allure.Description;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
@ -37,7 +37,7 @@ public class CreateAssetProfileTest extends AbstractDriverBaseTest {
private ProfilesPageHelper profilesPage;
private String name;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver);

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/DeleteAssetProfileTest.java

@ -17,7 +17,7 @@ package org.thingsboard.server.msa.ui.tests.assetProfileSmoke;
import io.qameta.allure.Description;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
@ -33,7 +33,7 @@ public class DeleteAssetProfileTest extends AbstractDriverBaseTest {
private SideBarMenuViewHelper sideBarMenuView;
private ProfilesPageHelper profilesPage;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver);

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/DeleteSeveralAssetProfilesTest.java

@ -17,7 +17,7 @@ package org.thingsboard.server.msa.ui.tests.assetProfileSmoke;
import io.qameta.allure.Description;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
@ -32,7 +32,7 @@ public class DeleteSeveralAssetProfilesTest extends AbstractDriverBaseTest {
private SideBarMenuViewHelper sideBarMenuView;
private ProfilesPageHelper profilesPage;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver);

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/MakeAssetProfileDefaultTest.java

@ -18,7 +18,7 @@ package org.thingsboard.server.msa.ui.tests.assetProfileSmoke;
import io.qameta.allure.Description;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
@ -34,7 +34,7 @@ public class MakeAssetProfileDefaultTest extends AbstractDriverBaseTest {
private ProfilesPageHelper profilesPage;
private String name;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver);

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/SearchAssetProfileTest.java

@ -18,7 +18,7 @@ package org.thingsboard.server.msa.ui.tests.assetProfileSmoke;
import io.qameta.allure.Description;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
@ -33,7 +33,7 @@ public class SearchAssetProfileTest extends AbstractDriverBaseTest {
private ProfilesPageHelper profilesPage;
private String name;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver);

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/SortByNameTest.java

@ -18,7 +18,7 @@ package org.thingsboard.server.msa.ui.tests.assetProfileSmoke;
import io.qameta.allure.Description;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
@ -33,7 +33,7 @@ public class SortByNameTest extends AbstractDriverBaseTest {
private ProfilesPageHelper profilesPage;
private String name;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver);

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/CreateCustomerTest.java

@ -19,7 +19,7 @@ import io.qameta.allure.Description;
import org.openqa.selenium.Keys;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.CustomerPageHelper;
@ -37,7 +37,7 @@ public class CreateCustomerTest extends AbstractDriverBaseTest {
private CustomerPageHelper customerPage;
private String customerName;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);

14
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/CustomerEditMenuTest.java

@ -18,6 +18,7 @@ package org.thingsboard.server.msa.ui.tests.customerSmoke;
import io.qameta.allure.Description;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
@ -38,16 +39,18 @@ import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultCustom
public class CustomerEditMenuTest extends AbstractDriverBaseTest {
private SideBarMenuViewElements sideBarMenuView;
private LoginPageHelper loginPage;
private CustomerPageHelper customerPage;
private DashboardPageHelper dashboardPage;
private String customerName;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
loginPage = new LoginPageHelper(driver);
sideBarMenuView = new SideBarMenuViewElements(driver);
customerPage = new CustomerPageHelper(driver);
dashboardPage = new DashboardPageHelper(driver);
loginPage.authorizationTenant();
}
@AfterMethod
@ -58,6 +61,13 @@ public class CustomerEditMenuTest extends AbstractDriverBaseTest {
}
}
@BeforeMethod
public void reLogin() {
if (getUrl().contains("/login")) {
loginPage.authorizationTenant();
}
}
@Test(priority = 10, groups = "smoke")
@Description
public void changeTitle() {

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/DeleteCustomerTest.java

@ -17,7 +17,7 @@ package org.thingsboard.server.msa.ui.tests.customerSmoke;
import io.qameta.allure.Description;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.CustomerPageHelper;
@ -35,7 +35,7 @@ public class DeleteCustomerTest extends AbstractDriverBaseTest {
private CustomerPageHelper customerPage;
private RuleChainsPageHelper ruleChainsPage;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/DeleteSeveralCustomerTest.java

@ -17,7 +17,7 @@ package org.thingsboard.server.msa.ui.tests.customerSmoke;
import io.qameta.allure.Description;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.CustomerPageHelper;
@ -33,7 +33,7 @@ public class DeleteSeveralCustomerTest extends AbstractDriverBaseTest {
private SideBarMenuViewElements sideBarMenuView;
private CustomerPageHelper customerPage;
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/ManageCustomersAssetsTest.java

@ -17,7 +17,7 @@ package org.thingsboard.server.msa.ui.tests.customerSmoke;
import io.qameta.allure.Description;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.CustomerPageHelper;
@ -30,7 +30,7 @@ public class ManageCustomersAssetsTest extends AbstractDriverBaseTest {
private CustomerPageHelper customerPage;
private final String manage = "Assets";
@BeforeMethod
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);

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

Loading…
Cancel
Save