diff --git a/application/src/main/data/json/demo/dashboards/gateways.json b/application/src/main/data/json/demo/dashboards/gateways.json
index 0a18b10a52..6051fbd514 100644
--- a/application/src/main/data/json/demo/dashboards/gateways.json
+++ b/application/src/main/data/json/demo/dashboards/gateways.json
@@ -131,7 +131,7 @@
"name": "Add device",
"icon": "add",
"type": "customPretty",
- "customHtml": "
\n",
+ "customHtml": "\n",
"customCss": "",
"customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenAddDeviceDialog();\n\nfunction openAddDeviceDialog() {\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\n}\n\nfunction AddDeviceDialogController(instance) {\n let vm = instance;\n \n vm.addDeviceFormGroup = vm.fb.group({\n deviceName: ['', [vm.validators.required]],\n deviceLabel: [''],\n attributes: vm.fb.group({\n latitude: [null],\n longitude: [null]\n }) \n });\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n vm.save = function() {\n vm.addDeviceFormGroup.markAsPristine();\n let device = {\n additionalInfo: {gateway: true},\n name: vm.addDeviceFormGroup.get('deviceName').value,\n type: 'gateway',\n label: vm.addDeviceFormGroup.get('deviceLabel').value\n };\n deviceService.saveDevice(device).subscribe(\n function (device) {\n saveAttributes(device.id).subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n function saveAttributes(entityId) {\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}\n",
"customResources": [],
@@ -160,7 +160,7 @@
"name": "Edit device",
"icon": "edit",
"type": "customPretty",
- "customHtml": "\n",
+ "customHtml": "\n",
"customCss": "/*=======================================================================*/\n/*========== There are two examples: for edit and add entity ==========*/\n/*=======================================================================*/\n/*======================== Edit entity example ========================*/\n/*=======================================================================*/\n/*\n.edit-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.edit-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.edit-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n.relations-list .header {\n padding-right: 5px;\n padding-bottom: 5px;\n padding-left: 5px;\n}\n\n.relations-list .header .cell {\n padding-right: 5px;\n padding-left: 5px;\n font-size: 12px;\n font-weight: 700;\n color: rgba(0, 0, 0, .54);\n white-space: nowrap;\n}\n\n.relations-list .body {\n padding-right: 5px;\n padding-bottom: 15px;\n padding-left: 5px;\n}\n\n.relations-list .body .row {\n padding-top: 5px;\n}\n\n.relations-list .body .cell {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.relations-list .body md-autocomplete-wrap md-input-container {\n height: 30px;\n}\n\n.relations-list .body .md-button {\n margin: 0;\n}\n\n.relations-list.old-relations tb-entity-select tb-entity-autocomplete button {\n display: none;\n} \n*/\n/*========================================================================*/\n/*========================= Add entity example =========================*/\n/*========================================================================*/\n/*\n.add-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.add-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.add-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n.relations-list .header {\n padding-right: 5px;\n padding-bottom: 5px;\n padding-left: 5px;\n}\n\n.relations-list .header .cell {\n padding-right: 5px;\n padding-left: 5px;\n font-size: 12px;\n font-weight: 700;\n color: rgba(0, 0, 0, .54);\n white-space: nowrap;\n}\n\n.relations-list .body {\n padding-right: 5px;\n padding-bottom: 15px;\n padding-left: 5px;\n}\n\n.relations-list .body .row {\n padding-top: 5px;\n}\n\n.relations-list .body .cell {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.relations-list .body md-autocomplete-wrap md-input-container {\n height: 30px;\n}\n\n.relations-list .body .md-button {\n margin: 0;\n}\n*/\n",
"customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenEditDeviceDialog();\n\nfunction openEditDeviceDialog() {\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\n}\n\nfunction EditDeviceDialogController(instance) {\n let vm = instance;\n \n vm.device = null;\n vm.attributes = {};\n \n vm.editDeviceFormGroup = vm.fb.group({\n deviceName: ['', [vm.validators.required]],\n deviceLabel: [''],\n attributes: vm.fb.group({\n latitude: [null],\n longitude: [null]\n }) \n });\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n vm.save = function() {\n vm.editDeviceFormGroup.markAsPristine();\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value;\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value;\n deviceService.saveDevice(vm.device).subscribe(\n function () {\n saveAttributes().subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n getEntityInfo();\n \n function getEntityInfo() {\n deviceService.getDevice(entityId.id).subscribe(\n function (device) {\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\n ['latitude', 'longitude']).subscribe(\n function (attributes) {\n for (let i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value; \n }\n vm.device = device;\n vm.editDeviceFormGroup.patchValue(\n {\n deviceName: vm.device.name,\n deviceLabel: vm.device.label,\n attributes: {\n latitude: vm.attributes.latitude,\n longitude: vm.attributes.longitude\n }\n }, {emitEvent: false}\n );\n } \n );\n }\n ); \n }\n \n function saveAttributes() {\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}\n",
"customResources": [],
diff --git a/application/src/main/data/json/demo/dashboards/thermostats.json b/application/src/main/data/json/demo/dashboards/thermostats.json
index 4f486effdf..c014d8875a 100644
--- a/application/src/main/data/json/demo/dashboards/thermostats.json
+++ b/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
}
]
},
diff --git a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json
index 448fcaf52c..e7a97b36f8 100644
--- a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json
+++ b/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}"
}
}
]
diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql
index d724f21a45..e2c2b10c96 100644
--- a/application/src/main/data/upgrade/3.4.4/schema_update.sql
+++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql
@@ -14,6 +14,70 @@
-- limitations under the License.
--
+-- USER CREDENTIALS START
+
+ALTER TABLE user_credentials
+ ADD COLUMN IF NOT EXISTS additional_info varchar NOT NULL DEFAULT '{}';
+
+UPDATE user_credentials
+ SET additional_info = json_build_object('userPasswordHistory', (u.additional_info::json -> 'userPasswordHistory'))
+ FROM tb_user u WHERE user_credentials.user_id = u.id AND u.additional_info::jsonb ? 'userPasswordHistory';
+
+UPDATE tb_user SET additional_info = tb_user.additional_info::jsonb - 'userPasswordHistory' WHERE additional_info::jsonb ? 'userPasswordHistory';
+
+-- USER CREDENTIALS END
+
+-- ALARM ASSIGN TO USER START
+
+ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assign_ts BIGINT;
+ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assignee_id UUID;
+
+CREATE INDEX IF NOT EXISTS idx_alarm_tenant_assignee_created_time ON alarm(tenant_id, assignee_id, created_time DESC);
+
+-- ALARM ASSIGN TO USER END
+
+-- ALARM STATUS REFACTORING START
+
+ALTER TABLE alarm ADD COLUMN IF NOT EXISTS acknowledged boolean;
+ALTER TABLE alarm ADD COLUMN IF NOT EXISTS cleared boolean;
+
+ALTER TABLE alarm ADD COLUMN IF NOT EXISTS status varchar; -- to avoid failure of the subsequent upgrade.
+UPDATE alarm SET acknowledged = true, cleared = true WHERE status = 'CLEARED_ACK';
+UPDATE alarm SET acknowledged = true, cleared = false WHERE status = 'ACTIVE_ACK';
+UPDATE alarm SET acknowledged = false, cleared = true WHERE status = 'CLEARED_UNACK';
+UPDATE alarm SET acknowledged = false, cleared = false WHERE status = 'ACTIVE_UNACK';
+
+-- Drop index by 'status' column and replace with new one that has only active alarms;
+DROP INDEX IF EXISTS idx_alarm_originator_alarm_type_active;
+CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type_active
+ ON alarm USING btree (originator_id, type) WHERE cleared = false;
+
+-- Cover index by alarm type to optimize propagated alarm queries;
+DROP INDEX IF EXISTS idx_entity_alarm_entity_id_alarm_type_created_time_alarm_id;
+CREATE INDEX IF NOT EXISTS idx_entity_alarm_entity_id_alarm_type_created_time_alarm_id ON entity_alarm
+USING btree (tenant_id, entity_id, alarm_type, created_time DESC) INCLUDE(alarm_id);
+
+DROP INDEX IF EXISTS idx_alarm_tenant_status_created_time;
+ALTER TABLE alarm DROP COLUMN IF EXISTS status;
+
+-- Update old alarms and set their state to clear, if there are newer alarms.
+UPDATE alarm a
+SET cleared = TRUE
+WHERE cleared = FALSE
+ AND id != (SELECT l.id
+ FROM alarm l
+ WHERE l.tenant_id = a.tenant_id
+ AND l.originator_id = a.originator_id
+ AND l.type = a.type
+ ORDER BY l.created_time DESC, l.id
+ LIMIT 1);
+
+VACUUM FULL ANALYZE alarm;
+
+-- ALARM STATUS REFACTORING END
+
+-- ALARM COMMENTS START
+
CREATE TABLE IF NOT EXISTS alarm_comment (
id uuid NOT NULL,
created_time bigint NOT NULL,
@@ -101,3 +165,256 @@ CREATE TABLE IF NOT EXISTS user_settings (
settings varchar(100000),
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE
);
+
+-- ALARM COMMENTS END
+
+-- ALARM INFO VIEW
+
+DROP VIEW IF EXISTS alarm_info CASCADE;
+CREATE VIEW alarm_info AS
+SELECT a.*,
+(CASE WHEN a.acknowledged AND a.cleared THEN 'CLEARED_ACK'
+ WHEN NOT a.acknowledged AND a.cleared THEN 'CLEARED_UNACK'
+ WHEN a.acknowledged AND NOT a.cleared THEN 'ACTIVE_ACK'
+ WHEN NOT a.acknowledged AND NOT a.cleared THEN 'ACTIVE_UNACK' END) as status,
+COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id)
+ WHEN a.originator_type = 1 THEN (select title from customer where id = a.originator_id)
+ WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
+ WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
+ WHEN a.originator_type = 4 THEN (select name from asset where id = a.originator_id)
+ WHEN a.originator_type = 5 THEN (select name from device where id = a.originator_id)
+ WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
+ WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id)
+ WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id)
+ WHEN a.originator_type = 18 THEN (select name from edge where id = a.originator_id) END
+ , 'Deleted') originator_name,
+COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id)
+ WHEN a.originator_type = 1 THEN (select COALESCE(title, email) from customer where id = a.originator_id)
+ WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
+ WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
+ WHEN a.originator_type = 4 THEN (select COALESCE(label, name) from asset where id = a.originator_id)
+ WHEN a.originator_type = 5 THEN (select COALESCE(label, name) from device where id = a.originator_id)
+ WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
+ WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id)
+ WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id)
+ WHEN a.originator_type = 18 THEN (select COALESCE(label, name) from edge where id = a.originator_id) END
+ , 'Deleted') as originator_label,
+u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email
+FROM alarm a
+LEFT JOIN tb_user u ON u.id = a.assignee_id;
+
+-- ALARM INFO VIEW END
+
+-- ALARM FUNCTIONS START
+
+DROP FUNCTION IF EXISTS create_or_update_active_alarm;
+CREATE OR REPLACE FUNCTION create_or_update_active_alarm(
+ t_id uuid, c_id uuid, a_id uuid, a_created_ts bigint,
+ a_o_id uuid, a_o_type integer, a_type varchar,
+ a_severity varchar, a_start_ts bigint, a_end_ts bigint,
+ a_details varchar,
+ a_propagate boolean, a_propagate_to_owner boolean,
+ a_propagate_to_tenant boolean, a_propagation_types varchar,
+ a_creation_enabled boolean)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ null_id constant uuid = '13814000-1dd2-11b2-8080-808080808080'::uuid;
+ existing alarm;
+ result alarm_info;
+ row_count integer;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.originator_id = a_o_id AND a.type = a_type AND a.cleared = false ORDER BY a.start_ts DESC FOR UPDATE;
+ IF existing.id IS NULL THEN
+ IF a_creation_enabled = FALSE THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+ IF c_id = null_id THEN
+ c_id = NULL;
+ end if;
+ INSERT INTO alarm
+ (tenant_id, customer_id, id, created_time,
+ originator_id, originator_type, type,
+ severity, start_ts, end_ts,
+ additional_info,
+ propagate, propagate_to_owner, propagate_to_tenant, propagate_relation_types,
+ acknowledged, ack_ts,
+ cleared, clear_ts,
+ assignee_id, assign_ts)
+ VALUES
+ (t_id, c_id, a_id, a_created_ts,
+ a_o_id, a_o_type, a_type,
+ a_severity, a_start_ts, a_end_ts,
+ a_details,
+ a_propagate, a_propagate_to_owner, a_propagate_to_tenant, a_propagation_types,
+ false, 0, false, 0, NULL, 0);
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ RETURN json_build_object('success', true, 'created', true, 'modified', true, 'alarm', row_to_json(result))::text;
+ ELSE
+ UPDATE alarm a
+ SET severity = a_severity,
+ start_ts = a_start_ts,
+ end_ts = a_end_ts,
+ additional_info = a_details,
+ propagate = a_propagate,
+ propagate_to_owner = a_propagate_to_owner,
+ propagate_to_tenant = a_propagate_to_tenant,
+ propagate_relation_types = a_propagation_types
+ WHERE a.id = existing.id
+ AND a.tenant_id = t_id
+ AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
+ OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
+ propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
+ GET DIAGNOSTICS row_count = ROW_COUNT;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = existing.id AND a.tenant_id = t_id;
+ IF row_count > 0 THEN
+ RETURN json_build_object('success', true, 'modified', true, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
+ ELSE
+ RETURN json_build_object('success', true, 'modified', false, 'alarm', row_to_json(result))::text;
+ END IF;
+ END IF;
+END
+$$;
+
+DROP FUNCTION IF EXISTS update_alarm;
+CREATE OR REPLACE FUNCTION update_alarm(t_id uuid, a_id uuid, a_severity varchar, a_start_ts bigint, a_end_ts bigint,
+ a_details varchar,
+ a_propagate boolean, a_propagate_to_owner boolean,
+ a_propagate_to_tenant boolean, a_propagation_types varchar)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ existing alarm;
+ result alarm_info;
+ row_count integer;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
+ IF existing IS NULL THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+ UPDATE alarm a
+ SET severity = a_severity,
+ start_ts = a_start_ts,
+ end_ts = a_end_ts,
+ additional_info = a_details,
+ propagate = a_propagate,
+ propagate_to_owner = a_propagate_to_owner,
+ propagate_to_tenant = a_propagate_to_tenant,
+ propagate_relation_types = a_propagation_types
+ WHERE a.id = a_id
+ AND a.tenant_id = t_id
+ AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
+ OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
+ propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
+ GET DIAGNOSTICS row_count = ROW_COUNT;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ IF row_count > 0 THEN
+ RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
+ ELSE
+ RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result))::text;
+ END IF;
+END
+$$;
+
+DROP FUNCTION IF EXISTS acknowledge_alarm;
+CREATE OR REPLACE FUNCTION acknowledge_alarm(t_id uuid, a_id uuid, a_ts bigint)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ existing alarm;
+ result alarm_info;
+ modified boolean = FALSE;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
+ IF existing IS NULL THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+
+ IF NOT (existing.acknowledged) THEN
+ modified = TRUE;
+ UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
+ END IF;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
+END
+$$;
+
+DROP FUNCTION IF EXISTS clear_alarm;
+CREATE OR REPLACE FUNCTION clear_alarm(t_id uuid, a_id uuid, a_ts bigint, a_details varchar)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ existing alarm;
+ result alarm_info;
+ cleared boolean = FALSE;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
+ IF existing IS NULL THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+ IF NOT(existing.cleared) THEN
+ cleared = TRUE;
+ UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id;
+ END IF;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text;
+END
+$$;
+
+DROP FUNCTION IF EXISTS assign_alarm;
+CREATE OR REPLACE FUNCTION assign_alarm(t_id uuid, a_id uuid, u_id uuid, a_ts bigint)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ existing alarm;
+ result alarm_info;
+ modified boolean = FALSE;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
+ IF existing IS NULL THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+ IF existing.assignee_id IS NULL OR existing.assignee_id != u_id THEN
+ modified = TRUE;
+ UPDATE alarm a SET assignee_id = u_id, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
+ END IF;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
+END
+$$;
+
+DROP FUNCTION IF EXISTS unassign_alarm;
+CREATE OR REPLACE FUNCTION unassign_alarm(t_id uuid, a_id uuid, a_ts bigint)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ existing alarm;
+ result alarm_info;
+ modified boolean = FALSE;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
+ IF existing IS NULL THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+ IF existing.assignee_id IS NOT NULL THEN
+ modified = TRUE;
+ UPDATE alarm a SET assignee_id = NULL, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
+ END IF;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
+END
+$$;
+
+-- ALARM FUNCTIONS END
diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
index d810fdede7..78ee6620fe 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
+++ b/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);
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 624a4ce3ef..1cdc1062d0 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -655,9 +655,7 @@ public abstract class BaseController {
}
AlarmInfo checkAlarmInfoId(AlarmId alarmId, Operation operation) throws ThingsboardException {
- return checkEntityId(alarmId, (tenantId, id) -> {
- return alarmService.findAlarmInfoByIdAsync(tenantId, alarmId).get();
- }, operation);
+ return checkEntityId(alarmId, alarmService::findAlarmInfoById, operation);
}
AlarmComment checkAlarmCommentId(AlarmCommentId alarmCommentId, AlarmId alarmId) throws ThingsboardException {
diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
index 768d98e403..53734b8633 100644
--- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
+++ b/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'";
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
index 716b7ef60f..1d429e9157 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
+++ b/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);
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
index fd1d5530d6..00bbf3b9e5 100644
--- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
@@ -87,6 +87,12 @@ public class EntityActionService {
case ALARM_CLEAR:
msgType = DataConstants.ALARM_CLEAR;
break;
+ case ALARM_ASSIGN:
+ msgType = DataConstants.ALARM_ASSIGN;
+ break;
+ case ALARM_UNASSIGN:
+ msgType = DataConstants.ALARM_UNASSIGN;
+ break;
case ALARM_DELETE:
msgType = DataConstants.ALARM_DELETE;
break;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
index 551412a756..d85750f7d2 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
+++ b/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;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
index 334a9aed0b..11ff6a3321 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
@@ -74,7 +74,7 @@ public abstract class AbstractTbEntityService {
protected ListenableFuture removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) {
ListenableFuture> 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> alarmIdsFuture = Futures.transform(alarmsFuture, page ->
page.getData().stream().map(AlarmInfo::getId).collect(Collectors.toList()), dbExecutor);
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
index a6620152ac..03a0671162 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
+++ b/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:
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java
index bae7ed96e0..26c38188fd 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java
@@ -38,8 +38,9 @@ public class DefaultTbAlarmCommentService extends AbstractTbEntityService implem
@Override
public AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException {
ActionType actionType = alarmComment.getId() == null ? ActionType.ADDED_COMMENT : ActionType.UPDATED_COMMENT;
- UserId userId = user.getId();
- alarmComment.setUserId(userId);
+ if (user != null) {
+ alarmComment.setUserId(user.getId());
+ }
try {
AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment));
notificationEntityService.notifyAlarmComment(alarm, savedAlarmComment, actionType, user);
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
index 8af0001e0b..154acf1129 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
@@ -15,9 +15,6 @@
*/
package org.thingsboard.server.service.entitiy.alarm;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.MoreExecutors;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -26,13 +23,19 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
-import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
+import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.audit.ActionType;
+import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UserId;
+import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import java.util.List;
@@ -50,9 +53,34 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
ActionType actionType = alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = alarm.getTenantId();
try {
- Alarm savedAlarm = checkNotNull(alarmSubscriptionService.createOrUpdateAlarm(alarm));
- notificationEntityService.notifyCreateOrUpdateAlarm(savedAlarm, actionType, user);
- return savedAlarm;
+ AlarmApiCallResult result;
+ if (alarm.getId() == null) {
+ result = alarmSubscriptionService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, user.getId()));
+ } else {
+ result = alarmSubscriptionService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm, user.getId()));
+ }
+ if (!result.isSuccessful()) {
+ throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
+ }
+ actionType = result.isCreated() ? ActionType.ADDED : ActionType.UPDATED;
+ if (result.isModified()) {
+ notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), actionType, user);
+ }
+ AlarmInfo resultAlarm = result.getAlarm();
+ if (alarm.isAcknowledged() && !resultAlarm.isAcknowledged()) {
+ resultAlarm = ack(resultAlarm, alarm.getAckTs(), user);
+ }
+ if (alarm.isCleared() && !resultAlarm.isCleared()) {
+ resultAlarm = clear(resultAlarm, alarm.getClearTs(), user);
+ }
+ UserId newAssignee = alarm.getAssigneeId();
+ UserId curAssignee = resultAlarm.getAssigneeId();
+ if (newAssignee != null && !newAssignee.equals(curAssignee)) {
+ resultAlarm = assign(alarm, newAssignee, alarm.getAssignTs(), user);
+ } else if (newAssignee == null && curAssignee != null) {
+ resultAlarm = unassign(alarm, alarm.getAssignTs(), user);
+ }
+ return new Alarm(resultAlarm);
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ALARM), alarm, actionType, user, e);
throw e;
@@ -60,51 +88,126 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
}
@Override
- public ListenableFuture ack(Alarm alarm, User user) {
- long ackTs = System.currentTimeMillis();
- ListenableFuture future = alarmSubscriptionService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs);
- return Futures.transform(future, result -> {
+ public AlarmInfo ack(Alarm alarm, User user) throws ThingsboardException {
+ return ack(alarm, System.currentTimeMillis(), user);
+ }
+
+ @Override
+ public AlarmInfo ack(Alarm alarm, long ackTs, User user) throws ThingsboardException {
+ AlarmApiCallResult result = alarmSubscriptionService.acknowledgeAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(ackTs));
+ if (!result.isSuccessful()) {
+ throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
+ }
+ if (result.isModified()) {
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was acknowledged by user %s",
- (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
- .put("userId", user.getId().toString()))
+ (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
+ .put("userId", user.getId().toString())
+ .put("subtype", "ACK"))
.build();
try {
alarmCommentService.saveAlarmComment(alarm, alarmComment, user);
} catch (ThingsboardException e) {
log.error("Failed to save alarm comment", e);
}
- alarm.setAckTs(ackTs);
- alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK);
- notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ACK, user);
- return null;
- }, MoreExecutors.directExecutor());
+ notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ACK, user);
+ } else {
+ throw new ThingsboardException("Alarm was already acknowledged!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+ }
+ return result.getAlarm();
}
@Override
- public ListenableFuture clear(Alarm alarm, User user) {
- long clearTs = System.currentTimeMillis();
- ListenableFuture future = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs);
- return Futures.transform(future, result -> {
+ public AlarmInfo clear(Alarm alarm, User user) throws ThingsboardException {
+ return clear(alarm, System.currentTimeMillis(), user);
+ }
+
+ @Override
+ public AlarmInfo clear(Alarm alarm, long clearTs, User user) throws ThingsboardException {
+ AlarmApiCallResult result = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(clearTs), null);
+ if (!result.isSuccessful()) {
+ throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
+ }
+ if (result.isCleared()) {
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was cleared by user %s",
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
- .put("userId", user.getId().toString()))
+ .put("userId", user.getId().toString())
+ .put("subtype", "CLEAR"))
+ .build();
+ try {
+ alarmCommentService.saveAlarmComment(alarm, alarmComment, user);
+ } catch (ThingsboardException e) {
+ log.error("Failed to save alarm comment", e);
+ }
+ notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_CLEAR, user);
+ } else {
+ throw new ThingsboardException("Alarm was already cleared!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+ }
+ return result.getAlarm();
+ }
+
+ @Override
+ public AlarmInfo assign(Alarm alarm, UserId assigneeId, long assignTs, User user) throws ThingsboardException {
+ AlarmApiCallResult result = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, getOrDefault(assignTs));
+ if (!result.isSuccessful()) {
+ throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
+ }
+ AlarmInfo alarmInfo = result.getAlarm();
+ if (result.isModified()) {
+ AlarmAssignee assignee = alarmInfo.getAssignee();
+ AlarmComment alarmComment = AlarmComment.builder()
+ .alarmId(alarm.getId())
+ .type(AlarmCommentType.SYSTEM)
+ .comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was assigned by user %s to user %s",
+ (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName(),
+ (assignee.getFirstName() == null || assignee.getLastName() == null) ? assignee.getEmail() : assignee.getFirstName() + " " + assignee.getLastName()))
+ .put("userId", user.getId().toString())
+ .put("assigneeId", assignee.getId().toString())
+ .put("subtype", "ASSIGN"))
+ .build();
+ try {
+ alarmCommentService.saveAlarmComment(alarm, alarmComment, user);
+ } catch (ThingsboardException e) {
+ log.error("Failed to save alarm comment", e);
+ }
+ notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ASSIGN, user);
+ } else {
+ throw new ThingsboardException("Alarm was already assigned to this user!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+ }
+ return alarmInfo;
+ }
+
+ @Override
+ public AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException {
+ AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(unassignTs));
+ if (!result.isSuccessful()) {
+ throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
+ }
+ AlarmInfo alarmInfo = result.getAlarm();
+ if (result.isModified()) {
+ AlarmComment alarmComment = AlarmComment.builder()
+ .alarmId(alarm.getId())
+ .type(AlarmCommentType.SYSTEM)
+ .comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was unassigned by user %s",
+ (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
+ .put("userId", user.getId().toString())
+ .put("subtype", "ASSIGN"))
.build();
try {
alarmCommentService.saveAlarmComment(alarm, alarmComment, user);
} catch (ThingsboardException e) {
log.error("Failed to save alarm comment", e);
}
- alarm.setClearTs(clearTs);
- alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK);
- notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user);
- return null;
- }, MoreExecutors.directExecutor());
+ notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGN, user);
+ } else {
+ throw new ThingsboardException("Alarm was already unassigned!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+ }
+ return alarmInfo;
}
@Override
@@ -115,4 +218,8 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
relatedEdgeIds, user, JacksonUtil.toString(alarm));
return alarmSubscriptionService.deleteAlarm(tenantId, alarm.getId());
}
-}
\ No newline at end of file
+
+ private static long getOrDefault(long ts) {
+ return ts > 0 ? ts : System.currentTimeMillis();
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
index f6746e20cc..a2ae9c8cc7 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
+++ b/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 ack(Alarm alarm, User user);
+ AlarmInfo ack(Alarm alarm, User user) throws ThingsboardException;
- ListenableFuture 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);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
index 13f8672d67..43d6a4df0a 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
+++ b/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 alarms = alarmDao.findAlarms(tenantId, alarmQuery);
boolean hasNext = true;
while (hasNext) {
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
index 3d5438f1e5..baded64603 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
@@ -27,7 +27,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
-import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -538,13 +538,14 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>() {});
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
index 54bc539559..48aee9cb50 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
@@ -26,6 +26,7 @@ import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -41,6 +42,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
+import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
@@ -258,7 +260,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
updateDeviceInactivityTimeout(tenantId, entityId, attributes);
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
- new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes))
+ new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes))
, null);
}
}
@@ -282,7 +284,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
@Override
- public void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) {
+ public void onAlarmUpdate(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback) {
onLocalAlarmSubUpdate(entityId,
s -> {
if (TbSubscriptionType.ALARMS.equals(s.getType())) {
@@ -291,16 +293,15 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
return null;
}
},
- s -> alarm.getCreatedTime() >= s.getTs(),
- s -> alarm,
- false
+ s -> alarm.getCreatedTime() >= s.getTs() || alarm.getAssignTs() >= s.getTs(),
+ alarm, false
);
notificationRuleProcessingService.process(tenantId, alarm, false);
callback.onSuccess();
}
@Override
- public void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) {
+ public void onAlarmDeleted(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback) {
onLocalAlarmSubUpdate(entityId,
s -> {
if (TbSubscriptionType.ALARMS.equals(s.getType())) {
@@ -310,8 +311,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
},
s -> alarm.getCreatedTime() >= s.getTs(),
- s -> alarm,
- true
+ alarm, true
);
notificationRuleProcessingService.process(tenantId, alarm, true);
callback.onSuccess();
@@ -451,19 +451,20 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
private void onLocalAlarmSubUpdate(EntityId entityId,
Function castFunction,
Predicate filterFunction,
- Function processFunction, boolean deleted) {
+ AlarmInfo alarm, boolean deleted) {
Set entitySubscriptions = subscriptionsByEntityId.get(entityId);
+ if (alarm == null) {
+ log.warn("[{}] empty alarm update!", entityId);
+ return;
+ }
if (entitySubscriptions != null) {
entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> {
- Alarm alarm = processFunction.apply(s);
- if (alarm != null) {
- if (serviceId.equals(s.getServiceId())) {
- AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, deleted);
- localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY);
- } else {
- TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId());
- toCoreNotificationsProducer.send(tpi, toProto(s, alarm, deleted), null);
- }
+ if (serviceId.equals(s.getServiceId())) {
+ AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, deleted);
+ localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY);
+ } else {
+ TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId());
+ toCoreNotificationsProducer.send(tpi, toProto(s, alarm, deleted), null);
}
});
} else {
@@ -594,12 +595,12 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
});
ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg(
- LocalSubscriptionServiceMsgProto.newBuilder().setSubUpdate(builder.build()).build())
+ LocalSubscriptionServiceMsgProto.newBuilder().setSubUpdate(builder.build()).build())
.build();
return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg);
}
- private TbProtoQueueMsg toProto(TbSubscription subscription, Alarm alarm, boolean deleted) {
+ private TbProtoQueueMsg toProto(TbSubscription subscription, AlarmInfo alarm, boolean deleted) {
TbAlarmSubscriptionUpdateProto.Builder builder = TbAlarmSubscriptionUpdateProto.newBuilder();
builder.setSessionId(subscription.getSessionId());
@@ -608,8 +609,8 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
builder.setDeleted(deleted);
ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg(
- LocalSubscriptionServiceMsgProto.newBuilder()
- .setAlarmSubUpdate(builder.build()).build())
+ LocalSubscriptionServiceMsgProto.newBuilder()
+ .setAlarmSubUpdate(builder.build()).build())
.build();
return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
index bbdd1a480a..1db4348828 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
@@ -17,12 +17,14 @@ package org.thingsboard.server.service.subscription;
import org.springframework.context.ApplicationListener;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
@@ -45,9 +47,9 @@ public interface SubscriptionManagerService extends ApplicationListener keys, TbCallback callback);
- void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
+ void onAlarmUpdate(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback);
- void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
+ void onAlarmDeleted(TenantId tenantId, EntityId entityId, AlarmInfo alarm, TbCallback callback);
void onNotificationUpdate(TenantId tenantId, UserId recipientId, NotificationUpdate notificationUpdate, TbCallback callback);
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
index 790b76fcaf..ccba16fbd8 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
+++ b/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 {
private final AlarmService alarmService;
@Getter
- @Setter
private final LinkedHashMap entitiesMap;
@Getter
- @Setter
private final HashMap alarmsMap;
private final int maxEntitiesPerAlarmSubscription;
@@ -225,8 +225,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx {
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 {
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;
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
index efbe57d56a..914c8a7c55 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
@@ -17,6 +17,8 @@ package org.thingsboard.server.service.subscription;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
@@ -229,8 +231,8 @@ public class TbSubscriptionUtils {
if (proto.getErrorCode() > 0) {
return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg());
} else {
- Alarm alarm = JacksonUtil.fromString(proto.getAlarm(), Alarm.class);
- return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm);
+ AlarmInfo alarm = JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class);
+ return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm, proto.getDeleted());
}
}
@@ -356,7 +358,7 @@ public class TbSubscriptionUtils {
return entry;
}
- public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, Alarm alarm) {
+ public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, AlarmInfo alarm) {
TbAlarmUpdateProto.Builder builder = TbAlarmUpdateProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name());
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
@@ -369,7 +371,7 @@ public class TbSubscriptionUtils {
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
}
- public static ToCoreMsg toAlarmDeletedProto(TenantId tenantId, EntityId entityId, Alarm alarm) {
+ public static ToCoreMsg toAlarmDeletedProto(TenantId tenantId, EntityId entityId, AlarmInfo alarm) {
TbAlarmDeleteProto.Builder builder = TbAlarmDeleteProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name());
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
index 52b4adf854..f412e76ad9 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
@@ -22,40 +22,39 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
+import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
+import org.thingsboard.server.common.data.alarm.AlarmModificationRequest;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
-import org.thingsboard.server.dao.alarm.AlarmCommentService;
+import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService;
-import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService;
-import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.Collection;
-import java.util.Optional;
/**
* Created by ashvayka on 27.03.18.
@@ -75,6 +74,41 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return "alarm";
}
+ @Override
+ public AlarmApiCallResult createAlarm(AlarmCreateOrUpdateActiveRequest request) {
+ boolean creationEnabled = apiUsageStateService.getApiUsageState(request.getTenantId()).isAlarmCreationEnabled();
+ var result = alarmService.createAlarm(request, creationEnabled);
+ if (result.isCreated()) {
+ apiUsageClient.report(request.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT);
+ }
+ return withWsCallback(request, result);
+ }
+
+ @Override
+ public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
+ return withWsCallback(alarmService.updateAlarm(request));
+ }
+
+ @Override
+ public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) {
+ return withWsCallback(alarmService.acknowledgeAlarm(tenantId, alarmId, ackTs));
+ }
+
+ @Override
+ public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details) {
+ return withWsCallback(alarmService.clearAlarm(tenantId, alarmId, clearTs, details));
+ }
+
+ @Override
+ public AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs) {
+ return withWsCallback(alarmService.assignAlarm(tenantId, alarmId, assigneeId, assignTs));
+ }
+
+ @Override
+ public AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs) {
+ return withWsCallback(alarmService.unassignAlarm(tenantId, alarmId, assignTs));
+ }
+
@Override
public Alarm createOrUpdateAlarm(Alarm alarm) {
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled());
@@ -102,29 +136,29 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
@Override
public Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId) {
- AlarmOperationResult result = alarmService.deleteAlarm(tenantId, alarmId);
+ AlarmApiCallResult result = alarmService.delAlarm(tenantId, alarmId);
onAlarmDeleted(result);
return result.isSuccessful();
}
@Override
public ListenableFuture ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) {
- ListenableFuture result = alarmService.ackAlarm(tenantId, alarmId, ackTs);
+ ListenableFuture 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 clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) {
- ListenableFuture 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 clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) {
- ListenableFuture result = alarmService.clearAlarm(tenantId, alarmId, details, clearTs);
- Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor);
- return result;
+ AlarmApiCallResult result = alarmService.clearAlarm(tenantId, alarmId, clearTs, details);
+ Futures.addCallback(Futures.immediateFuture(result), new AlarmUpdateCallback(), wsCallBackExecutor);
+ return Futures.immediateFuture(new AlarmOperationResult(result));
}
@Override
@@ -138,8 +172,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
}
@Override
- public ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) {
- return alarmService.findAlarmInfoByIdAsync(tenantId, alarmId);
+ public AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId) {
+ return alarmService.findAlarmInfoById(tenantId, alarmId);
}
@Override
@@ -153,8 +187,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
}
@Override
- public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) {
- return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus);
+ public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId) {
+ return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus, assigneeId);
}
@Override
@@ -162,15 +196,21 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return alarmService.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds);
}
+ @Override
+ public Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
+ return alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, type);
+ }
+
@Override
public ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
return alarmService.findLatestByOriginatorAndType(tenantId, originator, type);
}
+ @Deprecated
private void onAlarmUpdated(AlarmOperationResult result) {
wsCallBackExecutor.submit(() -> {
- Alarm alarm = result.getAlarm();
- TenantId tenantId = result.getAlarm().getTenantId();
+ AlarmInfo alarm = new AlarmInfo(result.getAlarm());
+ TenantId tenantId = alarm.getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY);
@@ -181,10 +221,24 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
});
}
- private void onAlarmDeleted(AlarmOperationResult result) {
+ private void onAlarmUpdated(AlarmApiCallResult result) {
wsCallBackExecutor.submit(() -> {
- Alarm alarm = result.getAlarm();
- TenantId tenantId = result.getAlarm().getTenantId();
+ AlarmInfo alarm = result.getAlarm();
+ TenantId tenantId = alarm.getTenantId();
+ for (EntityId entityId : result.getPropagatedEntitiesList()) {
+ forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
+ subscriptionManagerService.onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY);
+ }, () -> {
+ return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm);
+ });
+ }
+ });
+ }
+
+ private void onAlarmDeleted(AlarmApiCallResult result) {
+ wsCallBackExecutor.submit(() -> {
+ AlarmInfo alarm = result.getAlarm();
+ TenantId tenantId = alarm.getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAlarmDeleted(tenantId, entityId, alarm, TbCallback.EMPTY);
@@ -195,9 +249,9 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
});
}
- private class AlarmUpdateCallback implements FutureCallback {
+ private class AlarmUpdateCallback implements FutureCallback {
@Override
- public void onSuccess(@Nullable AlarmOperationResult result) {
+ public void onSuccess(@Nullable AlarmApiCallResult result) {
onAlarmUpdated(result);
}
@@ -207,4 +261,31 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
}
}
+ private AlarmApiCallResult withWsCallback(AlarmApiCallResult result) {
+ return withWsCallback(null, result);
+ }
+
+ private AlarmApiCallResult withWsCallback(AlarmModificationRequest request, AlarmApiCallResult result) {
+ if (result.isSuccessful() && result.isModified()) {
+ Futures.addCallback(Futures.immediateFuture(result), new AlarmUpdateCallback(), wsCallBackExecutor);
+ if (result.isSeverityChanged()) {
+ AlarmInfo alarm = result.getAlarm();
+ AlarmComment.AlarmCommentBuilder alarmComment = AlarmComment.builder()
+ .alarmId(alarm.getId())
+ .type(AlarmCommentType.SYSTEM)
+ .comment(JacksonUtil.newObjectNode().put("text",
+ String.format("Alarm severity was updated from %s to %s", result.getOldSeverity(), alarm.getSeverity())));
+ if (request != null && request.getUserId() != null) {
+ alarmComment.userId(request.getUserId());
+ }
+ try {
+ alarmCommentService.saveAlarmComment(alarm, alarmComment.build(), null);
+ } catch (ThingsboardException e) {
+ log.error("Failed to save alarm comment", e);
+ }
+ }
+ }
+ return result;
+ }
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
index 6ea3208208..ea059fcb2d 100644
--- a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
+++ b/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;
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java
index 7a1b159947..90041ffa13 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/sub/AlarmSubscriptionUpdate.java
@@ -16,7 +16,7 @@
package org.thingsboard.server.service.ws.telemetry.sub;
import lombok.Getter;
-import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.service.subscription.SubscriptionErrorCode;
public class AlarmSubscriptionUpdate {
@@ -28,15 +28,15 @@ public class AlarmSubscriptionUpdate {
@Getter
private String errorMsg;
@Getter
- private Alarm alarm;
+ private AlarmInfo alarm;
@Getter
private boolean alarmDeleted;
- public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm) {
+ public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarm) {
this(subscriptionId, alarm, false);
}
- public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, boolean alarmDeleted) {
+ public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarm, boolean alarmDeleted) {
super();
this.subscriptionId = subscriptionId;
this.alarm = alarm;
@@ -56,7 +56,7 @@ public class AlarmSubscriptionUpdate {
@Override
public String toString() {
- return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", alarm="
- + alarm + "]";
+ return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg +
+ ", alarm=" + alarm + "]";
}
-}
+}
\ No newline at end of file
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
index cb5caba133..cadf2c23be 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
@@ -172,6 +172,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected TenantId differentTenantId;
protected CustomerId differentCustomerId;
protected UserId customerUserId;
+ protected UserId differentCustomerUserId;
@SuppressWarnings("rawtypes")
private HttpMessageConverter mappingJackson2HttpMessageConverter;
@@ -371,7 +372,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
differentCustomerUser.setEmail(DIFFERENT_CUSTOMER_USER_EMAIL);
- createUserAndLogin(differentCustomerUser, DIFFERENT_CUSTOMER_USER_PASSWORD);
+ differentCustomerUser = createUserAndLogin(differentCustomerUser, DIFFERENT_CUSTOMER_USER_PASSWORD);
+ differentCustomerUserId = differentCustomerUser.getId();
}
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
index 7c8b3c4db8..8566d9d6a4 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
+++ b/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();
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
index 175f833c1c..777b97d940 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
+++ b/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>() {}
+ new TypeReference>() {
+ }
);
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 pageData = doGetTyped(
- "/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=1", new TypeReference>() {}
+ "/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=1", new TypeReference>() {
+ }
);
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;
}
}
diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java
index 5014280b07..1cf2cd44bb 100644
--- a/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java
+++ b/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);
diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
index f9916d27a0..93aeb17de6 100644
--- a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
+++ b/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
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java
new file mode 100644
index 0000000000..a8aebf16e7
--- /dev/null
+++ b/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 propagatedEntitiesList;
+
+ @Builder
+ private AlarmApiCallResult(boolean successful, boolean created, boolean modified, boolean cleared, AlarmInfo alarm, Alarm old, List 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 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()));
+ }
+
+}
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
index 6125d4a255..e3a05a4e2f 100644
--- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
+++ b/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 propagatedEntitiesList) {
this(alarm, successful, false, null, propagatedEntitiesList);
}
+
+ public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List 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();
+ }
}
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
index 2c60c41062..ad2aa1814f 100644
--- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
+++ b/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 ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
+ @Deprecated(since = "3.5.0", forRemoval = true)
ListenableFuture 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 findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
+
+ // Other API
Alarm findAlarmById(TenantId tenantId, AlarmId alarmId);
ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId);
- ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId);
+ AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId);
ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query);
ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query);
AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus,
- AlarmStatus alarmStatus);
+ AlarmStatus alarmStatus, String assigneeId);
- ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
+ Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
PageData findAlarmDataByQueryForEntities(TenantId tenantId,
AlarmDataQuery query, Collection orderedEntityIds);
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
index 9a9e000e4c..f2317efa05 100644
--- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
+++ b/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 fetchEntityName(TenantId tenantId, EntityId entityId);
+ Optional fetchEntityLabel(TenantId tenantId, EntityId entityId);
+
Optional fetchEntityCustomerId(TenantId tenantId, EntityId entityId);
+ Optional fetchNameLabelAndCustomerDetails(TenantId tenantId, EntityId entityId);
+
long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query);
PageData findEntityDataByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query);
-
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java b/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java
index efe92977cc..d8904f03e6 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java
+++ b/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 extends SearchTextBasedWithAdditionalInfo implements HasName {
+public abstract class ContactBased extends SearchTextBasedWithAdditionalInfo implements HasEmail {
private static final long serialVersionUID = 5047448057830660988L;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
index c9847b0078..d194fb7755 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
+++ b/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 implements HasTenantId, ExportableEntity {
+public class Customer extends ContactBased implements HasTenantId, ExportableEntity, HasTitle {
private static final long serialVersionUID = -1599722990298929275L;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
index e273e1b97e..7c166e53e3 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
+++ b/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 implements HasName, HasTenantId {
+public class DashboardInfo extends SearchTextBased implements HasName, HasTenantId, HasTitle {
private TenantId tenantId;
@NoXss
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
index 8f480bcf57..9eb18c76c5 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
@@ -74,6 +74,8 @@ public class DataConstants {
public static final String TIMESERIES_DELETED = "TIMESERIES_DELETED";
public static final String ALARM_ACK = "ALARM_ACK";
public static final String ALARM_CLEAR = "ALARM_CLEAR";
+ public static final String ALARM_ASSIGN = "ALARM_ASSIGN";
+ public static final String ALARM_UNASSIGN = "ALARM_UNASSIGN";
public static final String ALARM_DELETE = "ALARM_DELETE";
public static final String COMMENT_CREATED = "COMMENT_CREATED";
public static final String COMMENT_UPDATED = "COMMENT_UPDATED";
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java
index c1517e2230..27457bda34 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java
+++ b/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 implements HasName, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity {
+public class Device extends SearchTextBasedWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity {
private static final long serialVersionUID = 2807343040519543363L;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
index 216c33b2e8..472d73adb6 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
@@ -19,7 +19,30 @@ package org.thingsboard.server.common.data;
* @author Andrew Shvayka
*/
public enum EntityType {
- TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE,
- TENANT_PROFILE, DEVICE_PROFILE, ASSET_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC, QUEUE,
- NOTIFICATION_TARGET, NOTIFICATION_TEMPLATE, NOTIFICATION_REQUEST, NOTIFICATION, NOTIFICATION_RULE;
+ TENANT,
+ CUSTOMER,
+ USER,
+ DASHBOARD,
+ ASSET,
+ DEVICE,
+ ALARM,
+ RULE_CHAIN,
+ RULE_NODE,
+ ENTITY_VIEW,
+ WIDGETS_BUNDLE,
+ WIDGET_TYPE,
+ TENANT_PROFILE,
+ DEVICE_PROFILE,
+ ASSET_PROFILE,
+ API_USAGE_STATE,
+ TB_RESOURCE,
+ OTA_PACKAGE,
+ EDGE,
+ RPC,
+ QUEUE,
+ NOTIFICATION_TARGET,
+ NOTIFICATION_TEMPLATE,
+ NOTIFICATION_REQUEST,
+ NOTIFICATION,
+ NOTIFICATION_RULE
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java
new file mode 100644
index 0000000000..fd7fbf19fd
--- /dev/null
+++ b/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();
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java
new file mode 100644
index 0000000000..fe4f46c9de
--- /dev/null
+++ b/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();
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java
new file mode 100644
index 0000000000..aab1c6c346
--- /dev/null
+++ b/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();
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java
index c917d15910..c468cb24f8 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java
+++ b/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 implements HasName, HasTenantId {
+public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasTitle {
private static final long serialVersionUID = 3168391583570815419L;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
index f9c70a2c19..7e5210e344 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
+++ b/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 implements HasTenantId {
+public class Tenant extends ContactBased implements HasTenantId, HasTitle {
private static final long serialVersionUID = 8057243243859922101L;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
index 9db30fd466..34fec2cd82 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
+++ b/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 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 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 propagateRelationTypes;
@@ -97,11 +108,14 @@ public class Alarm extends BaseData 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 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 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;
+ }
+ }
+
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java
new file mode 100644
index 0000000000..20ed7b549c
--- /dev/null
+++ b/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;
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssigneeUpdate.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssigneeUpdate.java
new file mode 100644
index 0000000000..d1f32e927b
--- /dev/null
+++ b/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;
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java
new file mode 100644
index 0000000000..ecf882e2c9
--- /dev/null
+++ b/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();
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java
index 5483c519db..405097f6f5 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java
+++ b/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;
- }
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.java
new file mode 100644
index 0000000000..e40cb15d22
--- /dev/null
+++ b/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();
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java
new file mode 100644
index 0000000000..10069f8dbe
--- /dev/null
+++ b/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 propagateRelationTypes;
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
index a923b54abc..e4d0fb32df 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
+++ b/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;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSearchStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSearchStatus.java
index 92af75c8f0..4225f6efbb 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSearchStatus.java
+++ b/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 statuses;
+ ANY,
+ ACTIVE,
+ CLEARED,
+ ACK,
+ UNACK;
- AlarmSearchStatus(AlarmStatus... statuses) {
- this.statuses = new LinkedHashSet<>(Arrays.asList(statuses));
- }
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatusFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatusFilter.java
new file mode 100644
index 0000000000..c8f40fa048
--- /dev/null
+++ b/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 clearFilter;
+ private final Optional ackFilter;
+
+ private AlarmStatusFilter(Optional clearFilter, Optional 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 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 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 ack = Optional.empty();
+ if (ackFilter && !unackFilter || !ackFilter && unackFilter) {
+ ack = Optional.of(ackFilter);
+ }
+ return new AlarmStatusFilter(clear, ack);
+ }
+
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java
new file mode 100644
index 0000000000..23edafd6d2
--- /dev/null
+++ b/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();
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java
index e15557cdfe..8f25c0cbc5 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java
+++ b/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;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
index 0721b1b310..d5f4efa6c2 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
+++ b/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 implements HasName, HasTenantId, HasCustomerId, ExportableEntity {
+public class Asset extends SearchTextBasedWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, ExportableEntity {
private static final long serialVersionUID = 2807343040519543363L;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
index 876444a1dd..b479f1a236 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
+++ b/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),
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java
index 34c0c31428..4304abfb1d 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java
+++ b/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 implements HasName, HasTenantId, HasCustomerId {
+public class Edge extends SearchTextBasedWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId {
private static final long serialVersionUID = 4934987555236873728L;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java
index f57881a63b..9b813ae89c 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java
+++ b/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,
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
index d384c1acb5..3aaba1cf61 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
+++ b/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));
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NameLabelAndCustomerDetails.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NameLabelAndCustomerDetails.java
new file mode 100644
index 0000000000..8d7413b438
--- /dev/null
+++ b/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;
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java
index d63a5a95bd..1ae434f28c 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java
+++ b/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> 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<>();
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataPageLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataPageLink.java
index e46deddfd7..9c0253272c 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataPageLink.java
+++ b/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 statusList;
private List 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 typeList, List statusList, List severityList) {
+ List typeList, List statusList, List 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
);
}
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java
index 900fd966e8..c9bd9fbad2 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java
@@ -15,10 +15,20 @@
*/
package org.thingsboard.server.common.data.security;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.JsonNode;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.UserCredentialsId;
import org.thingsboard.server.common.data.id.UserId;
+import org.thingsboard.server.common.data.validation.Length;
+import org.thingsboard.server.common.data.validation.NoXss;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo.getJson;
+import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo.setJson;
@EqualsAndHashCode(callSuper = true)
public class UserCredentials extends BaseData {
@@ -30,6 +40,20 @@ public class UserCredentials extends BaseData {
private String password;
private String activateToken;
private String resetToken;
+
+ @NoXss
+ private transient JsonNode additionalInfo;
+
+ @JsonIgnore
+ private byte[] additionalInfoBytes;
+
+ public JsonNode getAdditionalInfo() {
+ return getJson(() -> additionalInfo, () -> additionalInfoBytes);
+ }
+
+ public void setAdditionalInfo(JsonNode settings) {
+ setJson(settings, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes);
+ }
public UserCredentials() {
super();
@@ -46,6 +70,7 @@ public class UserCredentials extends BaseData {
this.enabled = userCredentials.isEnabled();
this.activateToken = userCredentials.getActivateToken();
this.resetToken = userCredentials.getResetToken();
+ setAdditionalInfo(userCredentials.getAdditionalInfo());
}
public UserId getUserId() {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java
index d47876b268..a7e9fee82a 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java
+++ b/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 implements HasTenantId {
+public class BaseWidgetType extends BaseData implements HasName, HasTenantId {
private static final long serialVersionUID = 8388684344603660756L;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java
index 876438f834..679dead962 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java
+++ b/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 implements HasName, HasTenantId, ExportableEntity {
+public class WidgetsBundle extends SearchTextBased implements HasName, HasTenantId, ExportableEntity, HasTitle {
private static final long serialVersionUID = -7627368878362410489L;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
index 103f25a4ac..3d2e7078a1 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
+++ b/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 findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
+ Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
+
ListenableFuture findLatestByOriginatorAndTypeAsync(TenantId tenantId, EntityId originator, String type);
Alarm findAlarmById(TenantId tenantId, UUID key);
ListenableFuture findAlarmByIdAsync(TenantId tenantId, UUID key);
+ AlarmInfo findAlarmInfoById(TenantId tenantId, UUID key);
+
Alarm save(TenantId tenantId, Alarm alarm);
PageData findAlarms(TenantId tenantId, AlarmQuery query);
@@ -58,7 +66,7 @@ public interface AlarmDao extends Dao {
PageData findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection orderedEntityIds);
- Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set status);
+ Set findAlarmSeverities(TenantId tenantId, EntityId entityId, AlarmStatusFilter asf, String assigneeId);
PageData findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink);
@@ -67,4 +75,17 @@ public interface AlarmDao extends Dao {
List 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);
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
index 606eee468d..0dd2eebf87 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
+++ b/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 alarmDataValidator;
- @Autowired
- private EntityService entityService;
+ @Override
+ public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
+ validateAlarmRequest(request);
+ return withPropagated(alarmDao.updateAlarm(request));
+ }
- @Autowired
- private DataValidator 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 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 propagatedEntitiesList = createEntityAlarmRecords(saved);
- return new AlarmOperationResult(saved, true, true, null, propagatedEntitiesList);
+ return new AlarmOperationResult(saved, true, true, propagatedEntitiesList);
}
- private List createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException {
+ private List createEntityAlarmRecords(Alarm alarm) throws ExecutionException, InterruptedException {
Set propagatedEntitiesSet = new LinkedHashSet<>();
propagatedEntitiesSet.add(alarm.getOriginator());
if (alarm.isPropagate()) {
@@ -226,45 +268,41 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
@Override
public ListenableFuture ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTime) {
- return getAndUpdateAsync(tenantId, alarmId, new Function() {
- @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 clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTime) {
- return getAndUpdateAsync(tenantId, alarmId, new Function() {
- @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 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> findAlarms(TenantId tenantId, AlarmQuery query) {
- PageData 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> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) {
- PageData alarms = alarmDao.findCustomerAlarms(tenantId, customerId, query);
- if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) {
- return fetchAlarmsOriginators(tenantId, alarms);
- }
- return Futures.immediateFuture(alarms);
- }
-
- private ListenableFuture> fetchAlarmsOriginators(TenantId tenantId, PageData alarms) {
- List> 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 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 alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList);
-
+ Set 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 getPropagationEntityIdsList(Alarm alarm) {
+ return new ArrayList<>(getPropagationEntityIds(alarm));
+ }
+
private Set getPropagationEntityIds(Alarm alarm) {
if (alarm.isPropagate() || alarm.isPropagateToOwner() || alarm.isPropagateToTenant()) {
List 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 ListenableFuture getAndUpdateAsync(TenantId tenantId, AlarmId alarmId, Function function) {
- validateId(alarmId, "Alarm id should be specified!");
- ListenableFuture entity = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId());
- return Futures.transform(entity, function, readResultsProcessingExecutor);
- }
-
private T getAndUpdate(TenantId tenantId, AlarmId alarmId, Function 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 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());
+ }
+ }
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
index e6caddd80f..7aa785107e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
+++ b/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) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
index de6843e3a9..cbce9e48a1 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
+++ b/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 fetchEntityName(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchEntityName [{}]", entityId);
- EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType());
- Optional> 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 fetchEntityLabel(TenantId tenantId, EntityId entityId) {
+ log.trace("Executing fetchEntityLabel [{}]", entityId);
+ return fetchAndConvert(tenantId, entityId, this::getLabel);
}
@Override
public Optional fetchEntityCustomerId(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchEntityCustomerId [{}]", entityId);
+ return fetchAndConvert(tenantId, entityId, this::getCustomerId);
+ }
+
+ @Override
+ public Optional fetchNameLabelAndCustomerDetails(TenantId tenantId, EntityId entityId) {
+ log.trace("Executing fetchNameLabelAndCustomerDetails [{}]", entityId);
+ return fetchAndConvert(tenantId, entityId, this::getNameLabelAndCustomerDetails);
+ }
+
+ private Optional fetchAndConvert(TenantId tenantId, EntityId entityId, Function, T> converter) {
EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType());
- Optional> 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> 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())) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
index 6aa51bf7a3..0d4a5aa1a7 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
@@ -41,6 +41,7 @@ public class ModelConstants {
public static final String USER_ID_PROPERTY = "user_id";
public static final String TENANT_ID_PROPERTY = "tenant_id";
public static final String CUSTOMER_ID_PROPERTY = "customer_id";
+ public static final String ASSIGNEE_ID_PROPERTY = "assignee_id";
public static final String DEVICE_ID_PROPERTY = "device_id";
public static final String TITLE_PROPERTY = "title";
public static final String NAME_PROPERTY = "name";
@@ -82,6 +83,7 @@ public class ModelConstants {
public static final String USER_CREDENTIALS_PASSWORD_PROPERTY = "password"; //NOSONAR, the constant used to identify password column name (not password value itself)
public static final String USER_CREDENTIALS_ACTIVATE_TOKEN_PROPERTY = "activate_token";
public static final String USER_CREDENTIALS_RESET_TOKEN_PROPERTY = "reset_token";
+ public static final String USER_CREDENTIALS_ADDITIONAL_PROPERTY = "additional_info";
public static final String USER_CREDENTIALS_BY_USER_COLUMN_FAMILY_NAME = "user_credentials_by_user";
public static final String USER_CREDENTIALS_BY_ACTIVATE_TOKEN_COLUMN_FAMILY_NAME = "user_credentials_by_activate_token";
@@ -287,24 +289,35 @@ public class ModelConstants {
*/
public static final String ENTITY_ALARM_COLUMN_FAMILY_NAME = "entity_alarm";
public static final String ALARM_COLUMN_FAMILY_NAME = "alarm";
+ public static final String ALARM_VIEW_NAME = "alarm_info";
public static final String ALARM_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ALARM_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String ALARM_TYPE_PROPERTY = "type";
- public static final String ALARM_DETAILS_PROPERTY = "details";
+ public static final String ALARM_DETAILS_PROPERTY = ADDITIONAL_INFO_PROPERTY;
+ public static final String ALARM_STATUS_PROPERTY = "status";
public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id";
public static final String ALARM_ORIGINATOR_NAME_PROPERTY = "originator_name";
+ public static final String ALARM_ORIGINATOR_LABEL_PROPERTY = "originator_label";
public static final String ALARM_ORIGINATOR_TYPE_PROPERTY = "originator_type";
public static final String ALARM_SEVERITY_PROPERTY = "severity";
- public static final String ALARM_STATUS_PROPERTY = "status";
+ public static final String ALARM_ASSIGNEE_ID_PROPERTY = "assignee_id";
+ public static final String ALARM_ASSIGNEE_FIRST_NAME_PROPERTY = "assignee_first_name";
+ public static final String ALARM_ASSIGNEE_LAST_NAME_PROPERTY = "assignee_last_name";
+ public static final String ALARM_ASSIGNEE_EMAIL_PROPERTY = "assignee_email";
public static final String ALARM_START_TS_PROPERTY = "start_ts";
public static final String ALARM_END_TS_PROPERTY = "end_ts";
+ public static final String ALARM_ACKNOWLEDGED_PROPERTY = "acknowledged";
public static final String ALARM_ACK_TS_PROPERTY = "ack_ts";
+ public static final String ALARM_CLEARED_PROPERTY = "cleared";
public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts";
+ public static final String ALARM_ASSIGN_TS_PROPERTY = "assign_ts";
public static final String ALARM_PROPAGATE_PROPERTY = "propagate";
public static final String ALARM_PROPAGATE_TO_OWNER_PROPERTY = "propagate_to_owner";
public static final String ALARM_PROPAGATE_TO_TENANT_PROPERTY = "propagate_to_tenant";
public static final String ALARM_PROPAGATE_RELATION_TYPES = "propagate_relation_types";
+ public static final String ALARM_OPERATION_RESULT_PROPERTY = "operation_result";
+
public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id";
public static final String ALARM_COMMENT_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java
index cf05ccda02..164e1518a1 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java
+++ b/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 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 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 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 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 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 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 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 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(",")));
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java
index 1218613e2a..40ecd19a02 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java
+++ b/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 {
+ @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;
}
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java
index ff16525235..282bca89e8 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java
+++ b/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;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java
index 97868a1e07..5229c2cad6 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java
@@ -15,8 +15,10 @@
*/
package org.thingsboard.server.dao.model.sql;
+import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
+import org.hibernate.annotations.Type;
import org.thingsboard.server.common.data.id.UserCredentialsId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials;
@@ -50,6 +52,10 @@ public final class UserCredentialsEntity extends BaseSqlEntity
@Column(name = ModelConstants.USER_CREDENTIALS_RESET_TOKEN_PROPERTY, unique = true)
private String resetToken;
+ @Type(type = "json")
+ @Column(name = ModelConstants.USER_CREDENTIALS_ADDITIONAL_PROPERTY)
+ private JsonNode additionalInfo;
+
public UserCredentialsEntity() {
super();
}
@@ -66,6 +72,7 @@ public final class UserCredentialsEntity extends BaseSqlEntity
this.password = userCredentials.getPassword();
this.activateToken = userCredentials.getActivateToken();
this.resetToken = userCredentials.getResetToken();
+ this.additionalInfo = userCredentials.getAdditionalInfo();
}
@Override
@@ -79,6 +86,7 @@ public final class UserCredentialsEntity extends BaseSqlEntity
userCredentials.setPassword(password);
userCredentials.setActivateToken(activateToken);
userCredentials.setResetToken(resetToken);
+ userCredentials.setAdditionalInfo(additionalInfo);
return userCredentials;
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
index 43e9069fac..49e0257daf 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
+++ b/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 {
@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 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 {
"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 {
"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 {
@Param("affectedEntityType") String affectedEntityType,
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
- @Param("alarmStatuses") Set 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 findAllAlarms(@Param("tenantId") UUID tenantId,
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
- @Param("alarmStatuses") Set 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 {
@Param("customerId") UUID customerId,
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
- @Param("alarmStatuses") Set 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 {
"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 findAlarmSeverities(@Param("tenantId") UUID tenantId,
@Param("affectedEntityId") UUID affectedEntityId,
@Param("affectedEntityType") String affectedEntityType,
- @Param("alarmStatuses") Set 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 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);
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
index b8f508fef8..f1abd07d95 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
+++ b/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 implements A
return latest.isEmpty() ? null : DaoUtil.getData(latest.get(0));
}
+ @Override
+ public Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
+ List latest = alarmRepository.findLatestActiveByOriginatorAndType(
+ originator.getId(),
+ type,
+ PageRequest.of(0, 1));
+ return latest.isEmpty() ? null : DaoUtil.getData(latest.get(0));
+ }
+
@Override
public ListenableFuture findLatestByOriginatorAndTypeAsync(TenantId tenantId, EntityId originator, String type) {
return service.submit(() -> findLatestByOriginatorAndType(tenantId, originator, type));
@@ -97,6 +121,11 @@ public class JpaAlarmDao extends JpaAbstractDao 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 findAlarmByIdAsync(TenantId tenantId, UUID key) {
return findByIdAsync(tenantId, key);
@@ -106,11 +135,10 @@ public class JpaAlarmDao extends JpaAbstractDao implements A
public PageData 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 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 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 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 implements A
@Override
public PageData findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) {
log.trace("Try to find customer alarms by status [{}] and pageLink [{}]", query.getStatus(), query.getPageLink());
- Set 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 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 implements A
}
@Override
- public Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set statuses) {
- return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses);
+ public Set 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 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 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;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java
index 7943ce5178..a07887fb57 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java
+++ b/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;
}
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
index 543b8549c4..ecf9c1c95f 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
+++ b/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 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 findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection 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 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 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 toStatusSet(List statusList) {
- Set 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 ");
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
index 2710b84d03..c59efe69ff 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
@@ -127,7 +127,8 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
userCredentials.setEnabled(false);
userCredentials.setActivateToken(generateSafeToken(DEFAULT_TOKEN_LENGTH));
userCredentials.setUserId(new UserId(savedUser.getUuidId()));
- saveUserCredentialsAndPasswordHistory(user.getTenantId(), userCredentials);
+ userCredentials.setAdditionalInfo(JacksonUtil.newObjectNode());
+ userCredentialsDao.save(user.getTenantId(), userCredentials);
}
return savedUser;
}
@@ -157,7 +158,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
public UserCredentials saveUserCredentials(TenantId tenantId, UserCredentials userCredentials) {
log.trace("Executing saveUserCredentials [{}]", userCredentials);
userCredentialsValidator.validate(userCredentials, data -> tenantId);
- return saveUserCredentialsAndPasswordHistory(tenantId, userCredentials);
+ return userCredentialsDao.save(tenantId, userCredentials);
}
@Override
@@ -175,7 +176,9 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
userCredentials.setEnabled(true);
userCredentials.setActivateToken(null);
userCredentials.setPassword(password);
-
+ if (userCredentials.getPassword() != null) {
+ updatePasswordHistory(userCredentials);
+ }
return saveUserCredentials(tenantId, userCredentials);
}
@@ -211,7 +214,10 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
userCredentialsValidator.validate(userCredentials, data -> tenantId);
userCredentialsDao.removeById(tenantId, userCredentials.getUuidId());
userCredentials.setId(null);
- return saveUserCredentialsAndPasswordHistory(tenantId, userCredentials);
+ if (userCredentials.getPassword() != null) {
+ updatePasswordHistory(userCredentials);
+ }
+ return userCredentialsDao.save(tenantId, userCredentials);
}
@Override
@@ -347,17 +353,8 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
return failedLoginAttempts;
}
- private UserCredentials saveUserCredentialsAndPasswordHistory(TenantId tenantId, UserCredentials userCredentials) {
- UserCredentials result = userCredentialsDao.save(tenantId, userCredentials);
- User user = findUserById(tenantId, userCredentials.getUserId());
- if (userCredentials.getPassword() != null) {
- updatePasswordHistory(user, userCredentials);
- }
- return result;
- }
-
- private void updatePasswordHistory(User user, UserCredentials userCredentials) {
- JsonNode additionalInfo = user.getAdditionalInfo();
+ private void updatePasswordHistory(UserCredentials userCredentials) {
+ JsonNode additionalInfo = userCredentials.getAdditionalInfo();
if (!(additionalInfo instanceof ObjectNode)) {
additionalInfo = JacksonUtil.newObjectNode();
}
@@ -378,8 +375,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
userPasswordHistoryJson = JacksonUtil.valueToTree(userPasswordHistoryMap);
((ObjectNode) additionalInfo).set(USER_PASSWORD_HISTORY, userPasswordHistoryJson);
}
- user.setAdditionalInfo(additionalInfo);
- saveUser(user);
+ userCredentials.setAdditionalInfo(additionalInfo);
}
private final PaginatedRemover tenantAdminsRemover = new PaginatedRemover<>() {
diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql
index 8f742b6b1c..fb6940e2cb 100644
--- a/dao/src/main/resources/sql/schema-entities-idx.sql
+++ b/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);
diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql
index 373328f966..601b93f92f 100644
--- a/dao/src/main/resources/sql/schema-entities.sql
+++ b/dao/src/main/resources/sql/schema-entities.sql
@@ -52,13 +52,16 @@ CREATE TABLE IF NOT EXISTS alarm (
propagate boolean,
severity varchar(255),
start_ts bigint,
- status varchar(255),
+ assign_ts bigint,
+ assignee_id uuid,
tenant_id uuid,
customer_id uuid,
propagate_relation_types varchar,
type varchar(255),
propagate_to_owner boolean,
- propagate_to_tenant boolean
+ propagate_to_tenant boolean,
+ acknowledged boolean,
+ cleared boolean
);
CREATE TABLE IF NOT EXISTS alarm_comment (
@@ -427,13 +430,6 @@ CREATE TABLE IF NOT EXISTS relation (
additional_info varchar,
CONSTRAINT relation_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type)
);
--- ) PARTITION BY LIST (relation_type_group);
---
--- CREATE TABLE other_relations PARTITION OF relation DEFAULT;
--- CREATE TABLE common_relations PARTITION OF relation FOR VALUES IN ('COMMON');
--- CREATE TABLE alarm_relations PARTITION OF relation FOR VALUES IN ('ALARM');
--- CREATE TABLE dashboard_relations PARTITION OF relation FOR VALUES IN ('DASHBOARD');
--- CREATE TABLE rule_relations PARTITION OF relation FOR VALUES IN ('RULE_CHAIN', 'RULE_NODE');
CREATE TABLE IF NOT EXISTS tb_user (
id uuid NOT NULL CONSTRAINT tb_user_pkey PRIMARY KEY,
@@ -488,7 +484,8 @@ CREATE TABLE IF NOT EXISTS user_credentials (
enabled boolean,
password varchar(255),
reset_token varchar(255) UNIQUE,
- user_id uuid UNIQUE
+ user_id uuid UNIQUE,
+ additional_info varchar DEFAULT '{}'
);
CREATE TABLE IF NOT EXISTS widget_type (
@@ -858,3 +855,245 @@ CREATE TABLE IF NOT EXISTS user_settings (
settings varchar(10000),
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE
);
+
+DROP VIEW IF EXISTS alarm_info CASCADE;
+CREATE VIEW alarm_info AS
+SELECT a.*,
+(CASE WHEN a.acknowledged AND a.cleared THEN 'CLEARED_ACK'
+ WHEN NOT a.acknowledged AND a.cleared THEN 'CLEARED_UNACK'
+ WHEN a.acknowledged AND NOT a.cleared THEN 'ACTIVE_ACK'
+ WHEN NOT a.acknowledged AND NOT a.cleared THEN 'ACTIVE_UNACK' END) as status,
+COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id)
+ WHEN a.originator_type = 1 THEN (select title from customer where id = a.originator_id)
+ WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
+ WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
+ WHEN a.originator_type = 4 THEN (select name from asset where id = a.originator_id)
+ WHEN a.originator_type = 5 THEN (select name from device where id = a.originator_id)
+ WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
+ WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id)
+ WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id)
+ WHEN a.originator_type = 18 THEN (select name from edge where id = a.originator_id) END
+ , 'Deleted') originator_name,
+COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id)
+ WHEN a.originator_type = 1 THEN (select COALESCE(title, email) from customer where id = a.originator_id)
+ WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
+ WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
+ WHEN a.originator_type = 4 THEN (select COALESCE(label, name) from asset where id = a.originator_id)
+ WHEN a.originator_type = 5 THEN (select COALESCE(label, name) from device where id = a.originator_id)
+ WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
+ WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id)
+ WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id)
+ WHEN a.originator_type = 18 THEN (select COALESCE(label, name) from edge where id = a.originator_id) END
+ , 'Deleted') as originator_label,
+u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email
+FROM alarm a
+LEFT JOIN tb_user u ON u.id = a.assignee_id;
+
+CREATE OR REPLACE FUNCTION create_or_update_active_alarm(
+ t_id uuid, c_id uuid, a_id uuid, a_created_ts bigint,
+ a_o_id uuid, a_o_type integer, a_type varchar,
+ a_severity varchar, a_start_ts bigint, a_end_ts bigint,
+ a_details varchar,
+ a_propagate boolean, a_propagate_to_owner boolean,
+ a_propagate_to_tenant boolean, a_propagation_types varchar,
+ a_creation_enabled boolean)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ null_id constant uuid = '13814000-1dd2-11b2-8080-808080808080'::uuid;
+ existing alarm;
+ result alarm_info;
+ row_count integer;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.originator_id = a_o_id AND a.type = a_type AND a.cleared = false ORDER BY a.start_ts DESC FOR UPDATE;
+ IF existing.id IS NULL THEN
+ IF a_creation_enabled = FALSE THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+ IF c_id = null_id THEN
+ c_id = NULL;
+ end if;
+ INSERT INTO alarm
+ (tenant_id, customer_id, id, created_time,
+ originator_id, originator_type, type,
+ severity, start_ts, end_ts,
+ additional_info,
+ propagate, propagate_to_owner, propagate_to_tenant, propagate_relation_types,
+ acknowledged, ack_ts,
+ cleared, clear_ts,
+ assignee_id, assign_ts)
+ VALUES
+ (t_id, c_id, a_id, a_created_ts,
+ a_o_id, a_o_type, a_type,
+ a_severity, a_start_ts, a_end_ts,
+ a_details,
+ a_propagate, a_propagate_to_owner, a_propagate_to_tenant, a_propagation_types,
+ false, 0, false, 0, NULL, 0);
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ RETURN json_build_object('success', true, 'created', true, 'modified', true, 'alarm', row_to_json(result))::text;
+ ELSE
+ UPDATE alarm a
+ SET severity = a_severity,
+ start_ts = a_start_ts,
+ end_ts = a_end_ts,
+ additional_info = a_details,
+ propagate = a_propagate,
+ propagate_to_owner = a_propagate_to_owner,
+ propagate_to_tenant = a_propagate_to_tenant,
+ propagate_relation_types = a_propagation_types
+ WHERE a.id = existing.id
+ AND a.tenant_id = t_id
+ AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
+ OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
+ propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
+ GET DIAGNOSTICS row_count = ROW_COUNT;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = existing.id AND a.tenant_id = t_id;
+ IF row_count > 0 THEN
+ RETURN json_build_object('success', true, 'modified', true, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
+ ELSE
+ RETURN json_build_object('success', true, 'modified', false, 'alarm', row_to_json(result))::text;
+ END IF;
+ END IF;
+END
+$$;
+
+DROP FUNCTION IF EXISTS update_alarm;
+CREATE OR REPLACE FUNCTION update_alarm(t_id uuid, a_id uuid, a_severity varchar, a_start_ts bigint, a_end_ts bigint,
+ a_details varchar,
+ a_propagate boolean, a_propagate_to_owner boolean,
+ a_propagate_to_tenant boolean, a_propagation_types varchar)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ existing alarm;
+ result alarm_info;
+ row_count integer;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
+ IF existing IS NULL THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+ UPDATE alarm a
+ SET severity = a_severity,
+ start_ts = a_start_ts,
+ end_ts = a_end_ts,
+ additional_info = a_details,
+ propagate = a_propagate,
+ propagate_to_owner = a_propagate_to_owner,
+ propagate_to_tenant = a_propagate_to_tenant,
+ propagate_relation_types = a_propagation_types
+ WHERE a.id = a_id
+ AND a.tenant_id = t_id
+ AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
+ OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
+ propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
+ GET DIAGNOSTICS row_count = ROW_COUNT;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ IF row_count > 0 THEN
+ RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
+ ELSE
+ RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result))::text;
+ END IF;
+END
+$$;
+
+DROP FUNCTION IF EXISTS acknowledge_alarm;
+CREATE OR REPLACE FUNCTION acknowledge_alarm(t_id uuid, a_id uuid, a_ts bigint)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ existing alarm;
+ result alarm_info;
+ modified boolean = FALSE;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
+ IF existing IS NULL THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+
+ IF NOT (existing.acknowledged) THEN
+ modified = TRUE;
+ UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
+ END IF;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
+END
+$$;
+
+DROP FUNCTION IF EXISTS clear_alarm;
+CREATE OR REPLACE FUNCTION clear_alarm(t_id uuid, a_id uuid, a_ts bigint, a_details varchar)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ existing alarm;
+ result alarm_info;
+ cleared boolean = FALSE;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
+ IF existing IS NULL THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+ IF NOT(existing.cleared) THEN
+ cleared = TRUE;
+ UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id;
+ END IF;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text;
+END
+$$;
+
+DROP FUNCTION IF EXISTS assign_alarm;
+CREATE OR REPLACE FUNCTION assign_alarm(t_id uuid, a_id uuid, u_id uuid, a_ts bigint)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ existing alarm;
+ result alarm_info;
+ modified boolean = FALSE;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
+ IF existing IS NULL THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+ IF existing.assignee_id IS NULL OR existing.assignee_id != u_id THEN
+ modified = TRUE;
+ UPDATE alarm a SET assignee_id = u_id, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
+ END IF;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
+END
+$$;
+
+DROP FUNCTION IF EXISTS unassign_alarm;
+CREATE OR REPLACE FUNCTION unassign_alarm(t_id uuid, a_id uuid, a_ts bigint)
+ RETURNS varchar
+ LANGUAGE plpgsql
+AS
+$$
+DECLARE
+ existing alarm;
+ result alarm_info;
+ modified boolean = FALSE;
+BEGIN
+ SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
+ IF existing IS NULL THEN
+ RETURN json_build_object('success', false)::text;
+ END IF;
+ IF existing.assignee_id IS NOT NULL THEN
+ modified = TRUE;
+ UPDATE alarm a SET assignee_id = NULL, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
+ END IF;
+ SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
+ RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
+END
+$$;
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java
index 00eba295ae..688793d6c6 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java
+++ b/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();
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java
index dca45f100e..b7f5135533 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java
+++ b/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 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 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 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 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 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 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 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 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 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 alarmFields){
+ private AlarmDataQuery toQuery(AlarmDataPageLink pageLink, List 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 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 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());
diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java
index ef1d8fe737..ba3fe61f8a 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java
+++ b/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) {
diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java
index 45af699f67..5fbc2fd627 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java
+++ b/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 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);
}
+
}
diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
index 058d9c08fd..c2c0a6d794 100644
--- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
+++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
@@ -415,6 +415,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/clear", null, alarmId.getId());
}
+ public void assignAlarm(AlarmId alarmId, UserId userId) {
+ restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/assign/{userId}", null, alarmId.getId(), userId.getId());
+ }
+
+ public void unassignAlarm(AlarmId alarmId) {
+ restTemplate.delete(baseURL + "/api/alarm/{alarmId}/assign", alarmId.getId());
+ }
+
public PageData getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, Boolean fetchOriginator) {
String urlSecondPart = "/api/alarm/{entityType}/{entityId}?fetchOriginator={fetchOriginator}";
Map params = new HashMap<>();
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java
index 2620ecd069..e88411d73a 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java
@@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.api;
import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@@ -23,13 +24,17 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
+import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
+import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import java.util.Collection;
@@ -39,29 +44,65 @@ import java.util.Collection;
*/
public interface RuleEngineAlarmService {
- Alarm createOrUpdateAlarm(Alarm alarm);
+ /*
+ * New API, since 3.5.
+ */
- Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId);
+ /**
+ * Designed for atomic operations over active alarms.
+ * Only one active alarm may exist for the pair {originatorId, alarmType}
+ */
+ AlarmApiCallResult createAlarm(AlarmCreateOrUpdateActiveRequest request);
+ /**
+ * Designed to update existing alarm. Accepts only part of the alarm fields.
+ */
+ AlarmApiCallResult updateAlarm(AlarmUpdateRequest request);
+
+ AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
+
+ AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details);
+
+ AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs);
+
+ AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs);
+ /*
+ * Legacy API, before 3.5.
+ */
+ @Deprecated(since = "3.5", forRemoval = true)
+ Alarm createOrUpdateAlarm(Alarm alarm);
+
+ @Deprecated(since = "3.5", forRemoval = true)
ListenableFuture ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
+ @Deprecated(since = "3.5", forRemoval = true)
ListenableFuture clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
+ @Deprecated(since = "3.5", forRemoval = true)
ListenableFuture clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
+ // Other API
+ Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId);
+
ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId);
Alarm findAlarmById(TenantId tenantId, AlarmId alarmId);
+ Alarm findLatestActiveByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
+
ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
- ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId);
+ AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId);
+
+ default ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) {
+ return Futures.immediateFuture(findAlarmInfoById(tenantId, alarmId));
+ }
ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query);
ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query);
- AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus);
+ AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId);
PageData findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection orderedEntityIds);
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmResult.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmResult.java
index b5658a3451..3641d78b7c 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmResult.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmResult.java
@@ -17,7 +17,9 @@ package org.thingsboard.rule.engine.action;
import lombok.AllArgsConstructor;
import lombok.Data;
+import org.jetbrains.annotations.NotNull;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
@Data
@AllArgsConstructor
@@ -34,4 +36,8 @@ public class TbAlarmResult {
this.isCleared = isCleared;
this.alarm = alarm;
}
+
+ public static TbAlarmResult fromAlarmResult(AlarmApiCallResult result) {
+ return new TbAlarmResult(result.isCreated(), result.isModified(), result.isSeverityChanged(), result.isCleared(), result.getAlarm());
+ }
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java
index f94b4c36e2..ee32ac1533 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java
@@ -26,10 +26,10 @@ import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
@Slf4j
@RuleNode(
@@ -57,38 +57,29 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode