Browse Source

Merge branch 'develop/3.5.1' of github.com:thingsboard/thingsboard into feature/enrichment-rule-nodes-improvements

pull/8661/head
ShvaykaD 3 years ago
parent
commit
b8eb239c39
  1. 2
      application/pom.xml
  2. 276
      application/src/main/data/upgrade/3.4.4/schema_update.sql
  3. 10
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  4. 2
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  5. 2
      application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java
  6. 2
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  7. 4
      application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java
  8. 37
      application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java
  9. 9
      application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/TbWidgetsBundleService.java
  10. 22
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  11. 2
      application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java
  12. 7
      application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java
  13. 43
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java
  14. 4
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java
  15. 8
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java
  16. 2
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  17. 6
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  18. 9
      application/src/main/resources/thingsboard.yml
  19. 142
      application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java
  20. 2
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  21. 34
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  22. 3
      application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java
  23. 2
      common/actor/pom.xml
  24. 2
      common/cache/pom.xml
  25. 2
      common/cluster-api/pom.xml
  26. 2
      common/coap-server/pom.xml
  27. 2
      common/dao-api/pom.xml
  28. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java
  29. 2
      common/data/pom.xml
  30. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  31. 15
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java
  32. 35
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VcUtils.java
  33. 2
      common/edge-api/pom.xml
  34. 2
      common/message/pom.xml
  35. 2
      common/pom.xml
  36. 2
      common/queue/pom.xml
  37. 2
      common/script/pom.xml
  38. 2
      common/script/remote-js-client/pom.xml
  39. 2
      common/script/script-api/pom.xml
  40. 2
      common/stats/pom.xml
  41. 2
      common/transport/coap/pom.xml
  42. 2
      common/transport/http/pom.xml
  43. 2
      common/transport/lwm2m/pom.xml
  44. 2
      common/transport/mqtt/pom.xml
  45. 2
      common/transport/pom.xml
  46. 2
      common/transport/snmp/pom.xml
  47. 2
      common/transport/transport-api/pom.xml
  48. 4
      common/util/pom.xml
  49. 14
      common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java
  50. 2
      common/version-control/pom.xml
  51. 2
      dao/pom.xml
  52. 148
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  53. 2
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java
  54. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  55. 2
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java
  56. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java
  57. 16
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  58. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java
  59. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java
  60. 9
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java
  61. 267
      dao/src/main/resources/sql/schema-entities.sql
  62. 282
      dao/src/main/resources/sql/schema-views-and-functions.sql
  63. 1
      dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java
  64. 1
      dao/src/test/java/org/thingsboard/server/dao/TimescaleSqlInitializer.java
  65. 10
      dao/src/test/java/org/thingsboard/server/dao/service/install/sql/EntitiesSchemaSqlTest.java
  66. 2
      monitoring/pom.xml
  67. 2
      msa/black-box-tests/pom.xml
  68. 31
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
  69. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractBasePage.java
  70. 21
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java
  71. 39
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/AlarmWidgetElements.java
  72. 72
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CreateWidgetPopupElements.java
  73. 48
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CreateWidgetPopupHelper.java
  74. 1
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CustomerPageHelper.java
  75. 35
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DashboardPageElements.java
  76. 14
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DashboardPageHelper.java
  77. 117
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java
  78. 61
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java
  79. 6
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/SideBarMenuViewElements.java
  80. 14
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/SideBarMenuViewHelper.java
  81. 146
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assignee/AssignFromAlarmWidgetTest.java
  82. 45
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java
  83. 214
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java
  84. 75
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeleteDeviceTest.java
  85. 84
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeleteSeveralDevicesTest.java
  86. 179
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/EditDeviceTest.java
  87. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/RuleChainEditMenuTest.java
  88. 5
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/Const.java
  89. 12
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/DataProviderCredential.java
  90. 42
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/EntityPrototypes.java
  91. 10
      msa/black-box-tests/src/test/resources/all.xml
  92. 35
      msa/black-box-tests/src/test/resources/smokeDevices.xml
  93. 10
      msa/black-box-tests/src/test/resources/uiTests.xml
  94. 2
      msa/js-executor/package.json
  95. 2
      msa/js-executor/pom.xml
  96. 2
      msa/monitoring/pom.xml
  97. 2
      msa/pom.xml
  98. 2
      msa/tb-node/pom.xml
  99. 2
      msa/tb/docker-cassandra/Dockerfile
  100. 4
      msa/tb/pom.xml

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>

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

@ -14,32 +14,6 @@
-- limitations under the License.
--
-- DEVICE INFO VIEW START
DROP VIEW IF EXISTS device_info_active_attribute_view CASCADE;
CREATE OR REPLACE VIEW device_info_active_attribute_view AS
SELECT d.*
, c.title as customer_title
, COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public
, d.type as device_profile_name
, COALESCE(da.bool_v, FALSE) as active
FROM device d
LEFT JOIN customer c ON c.id = d.customer_id
LEFT JOIN attribute_kv da ON da.entity_id = d.id and da.attribute_key = 'active';
DROP VIEW IF EXISTS device_info_active_ts_view CASCADE;
CREATE OR REPLACE VIEW device_info_active_ts_view AS
SELECT d.*
, c.title as customer_title
, COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public
, d.type as device_profile_name
, COALESCE(dt.bool_v, FALSE) as active
FROM device d
LEFT JOIN customer c ON c.id = d.customer_id
LEFT JOIN ts_kv_latest dt ON dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active');
-- DEVICE INFO VIEW END
-- USER CREDENTIALS START
ALTER TABLE user_credentials
@ -203,256 +177,6 @@ CREATE TABLE IF NOT EXISTS user_settings (
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE,
CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, type)
);
-- ALARM INFO VIEW
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(NULLIF(title, ''), email) from customer where id = a.originator_id)
WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
WHEN a.originator_type = 4 THEN (select COALESCE(NULLIF(label, ''), name) from asset where id = a.originator_id)
WHEN a.originator_type = 5 THEN (select COALESCE(NULLIF(label, ''), name) from device where id = a.originator_id)
WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
WHEN a.originator_type = 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(NULLIF(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), 'old', row_to_json(existing))::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
-- TTL DROP PARTITIONS FUNCTIONS UPDATE START

10
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java

@ -70,8 +70,8 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
@Override
public void onUpdate(TbActorCtx context) throws Exception {
if (isMyNodePartition()) {
RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
if (isMyNodePartition(newRuleNode)) {
this.info = new RuleNodeInfo(entityId, ruleChainName, newRuleNode != null ? newRuleNode.getName() : "Unknown");
boolean restartRequired = state != ComponentLifecycleState.ACTIVE ||
!(ruleNode.getType().equals(newRuleNode.getType()) && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
@ -181,7 +181,11 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
private boolean isMyNodePartition() {
return !ruleNode.isSingletonMode()
return isMyNodePartition(this.ruleNode);
}
private boolean isMyNodePartition(RuleNode ruleNode) {
return ruleNode == null || !ruleNode.isSingletonMode()
|| systemContext.getDiscoveryService().isMonolith()
|| defaultCtx.isLocalEntity(ruleNode.getId());
}

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

@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings;
import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
import org.thingsboard.server.common.data.sync.vc.RepositorySettingsInfo;
import org.thingsboard.server.common.data.sync.vc.VcUtils;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
@ -305,6 +306,7 @@ public class AdminController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@PostMapping("/autoCommitSettings")
public AutoCommitSettings saveAutoCommitSettings(@RequestBody AutoCommitSettings settings) throws ThingsboardException {
settings.values().forEach(config -> VcUtils.checkBranchName(config.getBranch()));
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE);
return autoCommitSettingsService.save(getTenantId(), settings);
}

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

@ -117,7 +117,7 @@ public class WidgetsBundleController extends BaseController {
checkParameter("widgetsBundleId", strWidgetsBundleId);
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
WidgetsBundle widgetsBundle = checkWidgetsBundleId(widgetsBundleId, Operation.DELETE);
tbWidgetsBundleService.delete(widgetsBundle);
tbWidgetsBundleService.delete(widgetsBundle, getCurrentUser());
}
@ApiOperation(value = "Get Widget Bundles (getWidgetsBundles)",

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

@ -249,6 +249,7 @@ public class ThingsboardInstallService {
case "3.4.4":
log.info("Upgrading ThingsBoard from version 3.4.4 to 3.5.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.4.4");
entityDatabaseSchemaService.createOrUpdateViewsAndFunctions();
entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
if (!getEnv("SKIP_DEFAULT_NOTIFICATION_CONFIGS_CREATION", false)) {
systemDataLoaderService.createDefaultNotificationConfigs();
@ -279,6 +280,7 @@ public class ThingsboardInstallService {
entityDatabaseSchemaService.createDatabaseSchema();
entityDatabaseSchemaService.createOrUpdateViewsAndFunctions();
entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
log.info("Installing DataBase schema for timeseries...");

4
application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java

@ -67,7 +67,9 @@ public class DefaultEdgeInstallService implements EdgeInstallService {
dockerInstallInstructions = dockerInstallInstructions.replace("${LOCALHOST_WARNING}", "");
dockerInstallInstructions = dockerInstallInstructions.replace("${BASE_URL}", baseUrl);
}
dockerInstallInstructions = dockerInstallInstructions.replace("${TB_EDGE_VERSION}", appVersion + "EDGE");
String edgeVersion = appVersion + "EDGE";
edgeVersion = edgeVersion.replace("-SNAPSHOT", "");
dockerInstallInstructions = dockerInstallInstructions.replace("${TB_EDGE_VERSION}", edgeVersion);
dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_ROUTING_KEY}", edge.getRoutingKey());
dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_ROUTING_SECRET}", edge.getSecret());
dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_RPC_PORT}", Integer.toString(rpcPort));

37
application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java

@ -17,9 +17,10 @@ package org.thingsboard.server.service.entitiy.widgets.bundle;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -34,17 +35,31 @@ public class DefaultWidgetsBundleService extends AbstractTbEntityService impleme
@Override
public WidgetsBundle save(WidgetsBundle widgetsBundle, User user) throws Exception {
WidgetsBundle savedWidgetsBundle = checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle));
autoCommit(user, savedWidgetsBundle.getId());
notificationEntityService.notifySendMsgToEdgeService(widgetsBundle.getTenantId(), savedWidgetsBundle.getId(),
widgetsBundle.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
return savedWidgetsBundle;
ActionType actionType = widgetsBundle.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = widgetsBundle.getTenantId();
try {
WidgetsBundle savedWidgetsBundle = checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle));
autoCommit(user, savedWidgetsBundle.getId());
notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedWidgetsBundle.getId(),
savedWidgetsBundle, user, actionType, true, null);
return savedWidgetsBundle;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.WIDGETS_BUNDLE), widgetsBundle, actionType, user, e);
throw e;
}
}
@Override
public void delete(WidgetsBundle widgetsBundle) throws ThingsboardException {
widgetsBundleService.deleteWidgetsBundle(widgetsBundle.getTenantId(), widgetsBundle.getId());
notificationEntityService.notifySendMsgToEdgeService(widgetsBundle.getTenantId(), widgetsBundle.getId(),
EdgeEventActionType.DELETED);
public void delete(WidgetsBundle widgetsBundle, User user) {
TenantId tenantId = widgetsBundle.getTenantId();
try {
widgetsBundleService.deleteWidgetsBundle(widgetsBundle.getTenantId(), widgetsBundle.getId());
notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, widgetsBundle.getId(), widgetsBundle,
user, ActionType.DELETED, true, null);
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.WIDGETS_BUNDLE),
ActionType.DELETED, user, e, widgetsBundle.getId());
throw e;
}
}
}

9
application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/TbWidgetsBundleService.java

@ -15,13 +15,8 @@
*/
package org.thingsboard.server.service.entitiy.widgets.bundle;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
public interface TbWidgetsBundleService {
WidgetsBundle save(WidgetsBundle entity, User currentUser) throws Exception;
void delete(WidgetsBundle entity) throws ThingsboardException;
public interface TbWidgetsBundleService extends SimpleTbEntityService<WidgetsBundle> {
}

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

@ -21,6 +21,7 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -110,6 +111,8 @@ import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Service
@Profile("install")
@ -682,18 +685,29 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
}
@Override
@SneakyThrows
public void createDefaultNotificationConfigs() {
log.info("Creating default notification configs for system admin");
if (notificationTargetService.findNotificationTargetsByTenantId(TenantId.SYS_TENANT_ID, new PageLink(1)).getTotalElements() == 0) {
if (notificationTargetService.countNotificationTargetsByTenantId(TenantId.SYS_TENANT_ID) == 0) {
notificationSettingsService.createDefaultNotificationConfigs(TenantId.SYS_TENANT_ID);
}
PageDataIterable<TenantId> tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500);
ExecutorService executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors(), 4));
log.info("Creating default notification configs for all tenants");
AtomicInteger count = new AtomicInteger();
for (TenantId tenantId : tenants) {
if (notificationTargetService.findNotificationTargetsByTenantId(tenantId, new PageLink(1)).getTotalElements() == 0) {
notificationSettingsService.createDefaultNotificationConfigs(tenantId);
}
executor.submit(() -> {
if (notificationTargetService.countNotificationTargetsByTenantId(tenantId) == 0) {
notificationSettingsService.createDefaultNotificationConfigs(tenantId);
int n = count.incrementAndGet();
if (n % 500 == 0) {
log.info("{} tenants processed", n);
}
}
});
}
executor.shutdown();
executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
}

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

@ -19,4 +19,6 @@ public interface EntityDatabaseSchemaService extends DatabaseSchemaService {
void createOrUpdateDeviceInfoView(boolean activityStateInTelemetry);
void createOrUpdateViewsAndFunctions() throws Exception;
}

7
application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java

@ -27,6 +27,7 @@ public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSer
public static final String SCHEMA_ENTITIES_SQL = "schema-entities.sql";
public static final String SCHEMA_ENTITIES_IDX_SQL = "schema-entities-idx.sql";
public static final String SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL = "schema-entities-idx-psql-addon.sql";
public static final String SCHEMA_VIEWS_AND_FUNCTIONS_SQL = "schema-views-and-functions.sql";
public SqlEntityDatabaseSchemaService() {
super(SCHEMA_ENTITIES_SQL, SCHEMA_ENTITIES_IDX_SQL);
@ -45,4 +46,10 @@ public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSer
executeQuery("DROP VIEW IF EXISTS device_info_view CASCADE;");
executeQuery("CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM " + sourceViewName + ";");
}
@Override
public void createOrUpdateViewsAndFunctions() throws Exception {
log.info("Installing SQL DataBase schema views and functions: " + SCHEMA_VIEWS_AND_FUNCTIONS_SQL);
executeQueryFromFile(SCHEMA_VIEWS_AND_FUNCTIONS_SQL);
}
}

43
application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java

@ -15,13 +15,17 @@
*/
package org.thingsboard.server.service.notification.rule;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
@ -49,6 +53,8 @@ import org.thingsboard.server.service.notification.rule.cache.NotificationRulesC
import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor;
import javax.annotation.PostConstruct;
import java.io.Serializable;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
@ -71,9 +77,19 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess
@Autowired @Lazy
private NotificationCenter notificationCenter;
private final NotificationExecutorService notificationExecutor;
private final CacheManager cacheManager;
private Cache sentNotifications;
private final Map<NotificationRuleTriggerType, NotificationRuleTriggerProcessor> triggerProcessors = new EnumMap<>(NotificationRuleTriggerType.class);
@PostConstruct
private void init() {
sentNotifications = cacheManager.getCache(CacheConstants.SENT_NOTIFICATIONS_CACHE);
if (sentNotifications == null) {
throw new IllegalStateException("Sent notifications cache is not set up");
}
}
@Override
public void process(NotificationRuleTrigger trigger) {
NotificationRuleTriggerType triggerType = trigger.getType();
@ -126,6 +142,9 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess
log.debug("[{}] Rate limit for notification requests per rule was exceeded (rule '{}')", rule.getTenantId(), rule.getName());
return;
}
if (trigger.getType().isDeduplicate() && alreadySent(rule.getId(), trigger)) {
return;
}
NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig);
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
@ -175,6 +194,23 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess
return triggerProcessors.get(triggerConfig.getTriggerType()).constructNotificationInfo(trigger);
}
private boolean alreadySent(NotificationRuleId ruleId, NotificationRuleTrigger trigger) {
String key = ruleId + "_" + trigger.getOriginatorEntityId();
SentNotification sent = sentNotifications.get(key, SentNotification.class);
boolean alreadySent;
if (sent != null && sent.getTrigger().equals(trigger)) {
alreadySent = true;
log.debug("Notification for {} trigger was already sent, ignoring", trigger.getType());
// updating cache anyway so that the value is not removed by ttl
} else {
alreadySent = false;
sent = new SentNotification(trigger);
}
log.trace("[{}] Putting to sentNotifications cache: {}", ruleId, trigger);
sentNotifications.put(key, sent);
return alreadySent;
}
@EventListener(ComponentLifecycleMsg.class)
public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) {
if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED ||
@ -209,4 +245,11 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess
RuleEngineMsgTrigger.msgTypeToTriggerType = ruleEngineMsgTypeToTriggerType;
}
@Data
private static class SentNotification implements Serializable {
private static final long serialVersionUID = 38973480405095422L;
private final NotificationRuleTrigger trigger;
}
}

4
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java

@ -20,6 +20,7 @@ import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.info.DeviceActivityNotificationInfo;
@ -41,6 +42,9 @@ public class DeviceActivityTriggerProcessor implements RuleEngineMsgNotification
@Override
public boolean matchesFilter(RuleEngineMsgTrigger trigger, DeviceActivityNotificationRuleTriggerConfig triggerConfig) {
if (trigger.getMsg().getOriginator().getEntityType() != EntityType.DEVICE) {
return false;
}
DeviceEvent event = trigger.getMsg().getType().equals(DataConstants.ACTIVITY_EVENT) ? DeviceEvent.ACTIVE : DeviceEvent.INACTIVE;
if (!triggerConfig.getNotifyOn().contains(event)) {
return false;

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

@ -18,26 +18,18 @@ package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.discovery.PartitionService;
@Service
@RequiredArgsConstructor
public class NewPlatformVersionTriggerProcessor implements NotificationRuleTriggerProcessor<NewPlatformVersionTrigger, NewPlatformVersionNotificationRuleTriggerConfig> {
private final PartitionService partitionService;
@Override
public boolean matchesFilter(NewPlatformVersionTrigger trigger, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) {
if (!partitionService.isMyPartition(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID)) {
return false;
}
return trigger.getUpdateInfo().isUpdateAvailable();
}

2
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -473,7 +473,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
apiUsageReportClient.report(tenantId, null, ApiUsageRecordKey.ACTIVE_DEVICES, active);
apiUsageReportClient.report(tenantId, null, ApiUsageRecordKey.INACTIVE_DEVICES, inactive);
if (active > 0) {
log.info("[{}] Active devices: {}, inactive devices: {}", tenantId, active, inactive);
log.debug("[{}] Active devices: {}, inactive devices: {}", tenantId, active, inactive);
}
});
} catch (Throwable t) {

6
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java

@ -22,6 +22,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
@ -32,7 +33,6 @@ import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@ -101,6 +101,7 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import static com.google.common.util.concurrent.Futures.transform;
import static org.thingsboard.server.common.data.sync.vc.VcUtils.checkBranchName;
@Service
@TbCoreComponent
@ -138,6 +139,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
@SuppressWarnings("UnstableApiUsage")
@Override
public ListenableFuture<UUID> saveEntitiesVersion(User user, VersionCreateRequest request) throws Exception {
checkBranchName(request.getBranch());
var pendingCommit = gitServiceQueue.prepareCommit(user, request);
DonAsynchron.withCallback(pendingCommit, commit -> {
cachePut(commit.getTxId(), new VersionCreationResult());
@ -504,6 +506,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
@Override
public ListenableFuture<RepositorySettings> saveVersionControlSettings(TenantId tenantId, RepositorySettings versionControlSettings) {
checkBranchName(versionControlSettings.getDefaultBranch());
var restoredSettings = this.repositorySettingsService.restore(tenantId, versionControlSettings);
try {
var future = gitServiceQueue.initRepository(tenantId, restoredSettings);
@ -525,6 +528,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
@Override
public ListenableFuture<Void> checkVersionControlAccess(TenantId tenantId, RepositorySettings settings) throws ThingsboardException {
checkBranchName(settings.getDefaultBranch());
settings = this.repositorySettingsService.restore(tenantId, settings);
try {
return gitServiceQueue.testRepository(tenantId, settings);

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

@ -132,7 +132,7 @@ security:
githubMapper:
emailUrl: "${SECURITY_OAUTH2_GITHUB_MAPPER_EMAIL_URL_KEY:https://api.github.com/user/emails}"
java_cacerts:
path: "${SECURITY_JAVA_CACERTS_PATH:${java.home}${file.separator}lib${file.separator}security${file.separator}cacerts}"
path: "${SECURITY_JAVA_CACERTS_PATH:${java.home}/lib/security/cacerts}"
password: "${SECURITY_JAVA_CACERTS_PASSWORD:changeit}"
# Usage statistics parameters
@ -159,7 +159,7 @@ ui:
# Help parameters
help:
# Base url for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.4}"
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.5}"
database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
@ -449,6 +449,9 @@ cache:
notificationSettings:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_SETTINGS_TTL:10}"
maxSize: "${CACHE_SPECS_NOTIFICATION_SETTINGS_MAX_SIZE:1000}"
sentNotifications:
timeToLiveInMinutes: "${CACHE_SPECS_SENT_NOTIFICATIONS_TTL:1440}"
maxSize: "${CACHE_SPECS_SENT_NOTIFICATIONS_MAX_SIZE:10000}"
attributes:
timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}"
maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}"
@ -604,6 +607,7 @@ spring:
username: "${SPRING_DATASOURCE_USERNAME:postgres}"
password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
hikari:
leakDetectionThreshold: "${SPRING_DATASOURCE_HIKARI_LEAK_DETECTION_THRESHOLD:0}"
maximumPoolSize: "${SPRING_DATASOURCE_MAXIMUM_POOL_SIZE:16}"
registerMbeans: "${SPRING_DATASOURCE_HIKARI_REGISTER_MBEANS:false}" # true - enable MBean to diagnose pools state via JMX
@ -618,6 +622,7 @@ audit-log:
"device": "${AUDIT_LOG_MASK_DEVICE:W}"
"asset": "${AUDIT_LOG_MASK_ASSET:W}"
"dashboard": "${AUDIT_LOG_MASK_DASHBOARD:W}"
"widgets_bundle": "${AUDIT_LOG_MASK_WIDGETS_BUNDLE:W}"
"customer": "${AUDIT_LOG_MASK_CUSTOMER:W}"
"user": "${AUDIT_LOG_MASK_USER:W}"
"rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}"

142
application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java

@ -21,16 +21,15 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList;
@ -39,7 +38,6 @@ import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.SYSTEM_TENANT;
@DaoSqlTest
public class WidgetsBundleControllerTest extends AbstractControllerTest {
@ -72,7 +70,7 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@ -81,14 +79,14 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("My widgets bundle");
Mockito.reset(tbClusterService);
Mockito.reset(tbClusterService, auditLogService);
WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(savedWidgetsBundle, savedWidgetsBundle,
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, ActionType.ADDED, 0, 1, 0);
Mockito.reset(tbClusterService);
testNotifyEntityAllOneTime(savedWidgetsBundle, savedWidgetsBundle.getId(), savedWidgetsBundle.getId(), savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED);
Mockito.reset(tbClusterService, auditLogService);
Assert.assertNotNull(savedWidgetsBundle);
Assert.assertNotNull(savedWidgetsBundle.getId());
@ -103,25 +101,26 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
WidgetsBundle foundWidgetsBundle = doGet("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString(), WidgetsBundle.class);
Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle());
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(savedWidgetsBundle, savedWidgetsBundle,
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.UPDATED, ActionType.UPDATED, 0, 1, 0);
testNotifyEntityAllOneTime(savedWidgetsBundle, savedWidgetsBundle.getId(), savedWidgetsBundle.getId(), savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED);
}
@Test
public void testSaveWidgetBundleWithViolationOfLengthValidation() throws Exception {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle(StringUtils.randomAlphabetic(300));
@Test
public void testSaveWidgetBundleWithViolationOfLengthValidation() throws Exception {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle(StringUtils.randomAlphabetic(300));
Mockito.reset(tbClusterService);
Mockito.reset(tbClusterService, auditLogService);
String msgError = msgErrorFieldLength("title");
doPost("/api/widgetsBundle", widgetsBundle)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
String msgError = msgErrorFieldLength("title");
doPost("/api/widgetsBundle", widgetsBundle)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityNever(widgetsBundle.getId(), widgetsBundle);
}
testNotifyEntityEqualsOneTimeServiceNeverError(widgetsBundle, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED,
new DataValidationException("Validation error: title length must be equal or less than 255"));
}
@Test
public void testUpdateWidgetsBundleFromDifferentTenant() throws Exception {
@ -131,7 +130,7 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
loginDifferentTenant();
Mockito.reset(tbClusterService);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/widgetsBundle", savedWidgetsBundle)
.andExpect(status().isForbidden())
@ -157,26 +156,25 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("My widgets bundle");
Mockito.reset(tbClusterService, auditLogService);
WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
doDelete("/api/widgetsBundle/"+savedWidgetsBundle.getId().getId().toString())
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString())
.andExpect(status().isOk());
String savedWidgetsBundleIdStr = savedWidgetsBundle.getId().getId().toString();
testNotifyEntityAllOneTime(savedWidgetsBundle, savedWidgetsBundle.getId(), savedWidgetsBundle.getId(), savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED);
doGet("/api/widgetsBundle/" + savedWidgetsBundleIdStr)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Widgets bundle", savedWidgetsBundleIdStr))));
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(savedWidgetsBundle, savedWidgetsBundle,
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.DELETED, ActionType.DELETED, 0, 1, 0);
}
@Test
public void testSaveWidgetsBundleWithEmptyTitle() throws Exception {
Mockito.reset(tbClusterService, auditLogService);
WidgetsBundle widgetsBundle = new WidgetsBundle();
@ -184,7 +182,9 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Widgets bundle title " + msgErrorShouldBeSpecified)));
testNotifyEntityNever(widgetsBundle.getId(), widgetsBundle);
testNotifyEntityEqualsOneTimeServiceNeverError(widgetsBundle, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED,
new DataValidationException("Widgets bundle title should be specified!"));
}
@Test
@ -194,13 +194,14 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
savedWidgetsBundle.setAlias("new_alias");
Mockito.clearInvocations(tbClusterService);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/widgetsBundle", savedWidgetsBundle)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Update of widgets bundle alias is prohibited")));
testNotifyEntityNever(savedWidgetsBundle.getId(), savedWidgetsBundle);
testNotifyEntityEqualsOneTimeServiceNeverError(savedWidgetsBundle, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED,
new DataValidationException("Update of widgets bundle alias is prohibited!"));
}
@Test
@ -209,22 +210,17 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
login(tenantAdmin.getEmail(), "testPassword1");
List<WidgetsBundle> sysWidgetsBundles = doGetTyped("/api/widgetsBundles?",
new TypeReference<>(){});
Mockito.reset(tbClusterService);
new TypeReference<>() {
});
int cntEntity = 73;
List<WidgetsBundle> widgetsBundles = new ArrayList<>();
for (int i=0;i<cntEntity;i++) {
for (int i = 0; i < cntEntity; i++) {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("Widgets bundle"+i);
widgetsBundle.setTitle("Widgets bundle" + i);
widgetsBundles.add(doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class));
}
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new WidgetsBundle(), new WidgetsBundle(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, ActionType.ADDED, 0, cntEntity, 0);
widgetsBundles.addAll(sysWidgetsBundles);
List<WidgetsBundle> loadedWidgetsBundles = new ArrayList<>();
@ -232,7 +228,8 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
PageData<WidgetsBundle> pageData;
do {
pageData = doGetTypedWithPageLink("/api/widgetsBundles?",
new TypeReference<>(){}, pageLink);
new TypeReference<>() {
}, pageLink);
loadedWidgetsBundles.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -251,13 +248,14 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
loginSysAdmin();
List<WidgetsBundle> sysWidgetsBundles = doGetTyped("/api/widgetsBundles?",
new TypeReference<>(){});
new TypeReference<>() {
});
int cntEntity = 120;
List<WidgetsBundle> createdWidgetsBundles = new ArrayList<>();
for (int i=0;i<cntEntity;i++) {
for (int i = 0; i < cntEntity; i++) {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("Widgets bundle"+i);
widgetsBundle.setTitle("Widgets bundle" + i);
createdWidgetsBundles.add(doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class));
}
@ -269,7 +267,8 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
PageData<WidgetsBundle> pageData;
do {
pageData = doGetTypedWithPageLink("/api/widgetsBundles?",
new TypeReference<>(){}, pageLink);
new TypeReference<>() {
}, pageLink);
loadedWidgetsBundles.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -281,22 +280,17 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
Assert.assertEquals(widgetsBundles, loadedWidgetsBundles);
Mockito.reset(tbClusterService);
for (WidgetsBundle widgetsBundle : createdWidgetsBundles) {
doDelete("/api/widgetsBundle/"+widgetsBundle.getId().getId().toString())
doDelete("/api/widgetsBundle/" + widgetsBundle.getId().getId().toString())
.andExpect(status().isOk());
}
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new WidgetsBundle(), new WidgetsBundle(),
SYSTEM_TENANT, (CustomerId) createEntityId_NULL_UUID(new Customer()), null, SYS_ADMIN_EMAIL,
ActionType.DELETED, ActionType.DELETED, 0, cntEntity, 0);
pageLink = new PageLink(17);
loadedWidgetsBundles.clear();
do {
pageData = doGetTypedWithPageLink("/api/widgetsBundles?",
new TypeReference<PageData<WidgetsBundle>>(){}, pageLink);
new TypeReference<PageData<WidgetsBundle>>() {
}, pageLink);
loadedWidgetsBundles.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -316,19 +310,21 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
login(tenantAdmin.getEmail(), "testPassword1");
List<WidgetsBundle> sysWidgetsBundles = doGetTyped("/api/widgetsBundles?",
new TypeReference<List<WidgetsBundle>>(){});
new TypeReference<List<WidgetsBundle>>() {
});
List<WidgetsBundle> widgetsBundles = new ArrayList<>();
for (int i=0;i<73;i++) {
for (int i = 0; i < 73; i++) {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("Widgets bundle"+i);
widgetsBundle.setTitle("Widgets bundle" + i);
widgetsBundles.add(doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class));
}
widgetsBundles.addAll(sysWidgetsBundles);
List<WidgetsBundle> loadedWidgetsBundles = doGetTyped("/api/widgetsBundles?",
new TypeReference<>(){});
new TypeReference<>() {
});
Collections.sort(widgetsBundles, idComparator);
Collections.sort(loadedWidgetsBundles, idComparator);
@ -343,12 +339,13 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
List<WidgetsBundle> sysWidgetsBundles = doGetTyped("/api/widgetsBundles?",
new TypeReference<>(){});
new TypeReference<>() {
});
List<WidgetsBundle> createdSystemWidgetsBundles = new ArrayList<>();
for (int i=0;i<82;i++) {
for (int i = 0; i < 82; i++) {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("Sys widgets bundle"+i);
widgetsBundle.setTitle("Sys widgets bundle" + i);
createdSystemWidgetsBundles.add(doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class));
}
@ -360,14 +357,15 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
login(tenantAdmin.getEmail(), "testPassword1");
for (int i=0;i<127;i++) {
for (int i = 0; i < 127; i++) {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("Tenant widgets bundle"+i);
widgetsBundle.setTitle("Tenant widgets bundle" + i);
widgetsBundles.add(doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class));
}
List<WidgetsBundle> loadedWidgetsBundles = doGetTyped("/api/widgetsBundles?",
new TypeReference<List<WidgetsBundle>>(){});
new TypeReference<List<WidgetsBundle>>() {
});
Collections.sort(widgetsBundles, idComparator);
Collections.sort(loadedWidgetsBundles, idComparator);
@ -377,7 +375,8 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
loginSysAdmin();
loadedWidgetsBundles = doGetTyped("/api/widgetsBundles?",
new TypeReference<List<WidgetsBundle>>(){});
new TypeReference<>() {
});
Collections.sort(systemWidgetsBundles, idComparator);
Collections.sort(loadedWidgetsBundles, idComparator);
@ -385,12 +384,13 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
Assert.assertEquals(systemWidgetsBundles, loadedWidgetsBundles);
for (WidgetsBundle widgetsBundle : createdSystemWidgetsBundles) {
doDelete("/api/widgetsBundle/"+widgetsBundle.getId().getId().toString())
doDelete("/api/widgetsBundle/" + widgetsBundle.getId().getId().toString())
.andExpect(status().isOk());
}
loadedWidgetsBundles = doGetTyped("/api/widgetsBundles?",
new TypeReference<>(){});
new TypeReference<>() {
});
Collections.sort(sysWidgetsBundles, idComparator);
Collections.sort(loadedWidgetsBundles, idComparator);

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

@ -204,7 +204,7 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
NotificationTemplate template = createNotificationTemplate(NotificationType.valueOf(triggerConfig.getTriggerType().toString()), subject, text, NotificationDeliveryMethod.WEB);
NotificationRule rule = new NotificationRule();
rule.setName(triggerConfig.getTriggerType() + " [" + Arrays.toString(targets) + "]");
rule.setName(triggerConfig.getTriggerType() + " " + Arrays.toString(targets));
rule.setTemplateId(template.getId());
rule.setTriggerType(triggerConfig.getTriggerType());
rule.setTriggerConfig(triggerConfig);

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

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
@ -55,6 +56,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotific
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -66,9 +68,11 @@ import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
import org.thingsboard.server.service.apiusage.limits.LimitedApi;
import org.thingsboard.server.service.apiusage.limits.RateLimitService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
@ -102,6 +106,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
private RateLimitService rateLimitService;
@Autowired
private RuleChainService ruleChainService;
@Autowired
private NotificationRuleProcessor notificationRuleProcessor;
@Before
public void beforeEach() throws Exception {
@ -433,6 +439,34 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
assertThat(getWsClient().getLastCountUpdate().getTotalUnreadCount()).isEqualTo(notificationRequestsLimit);
}
@Test
public void testNotificationsDeduplication() throws Exception {
loginSysAdmin();
NewPlatformVersionNotificationRuleTriggerConfig triggerConfig = new NewPlatformVersionNotificationRuleTriggerConfig();
createNotificationRule(triggerConfig, "Test", "Test", createNotificationTarget(tenantAdminUserId).getId());
loginTenantAdmin();
assertThat(getMyNotifications(false, 100)).size().isZero();
for (int i = 1; i <= 10; i++) {
notificationRuleProcessor.process(NewPlatformVersionTrigger.builder()
.updateInfo(new UpdateMessage(true, "test", "test",
"test", "test", "test"))
.build());
TimeUnit.MILLISECONDS.sleep(300);
}
TimeUnit.SECONDS.sleep(5);
assertThat(getMyNotifications(false, 100)).size().isOne();
notificationRuleProcessor.process(NewPlatformVersionTrigger.builder()
.updateInfo(new UpdateMessage(true, "CHANGED", "test",
"test", "test", "test"))
.build());
await().atMost(5, TimeUnit.SECONDS)
.untilAsserted(() -> {
assertThat(getMyNotifications(false, 100)).size().isEqualTo(2);
});
}
private <R> R checkNotificationAfter(Callable<R> action, BiConsumer<Notification, R> check) throws Exception {
if (getWsClient().getLastDataUpdate() == null) {
getWsClient().subscribeForUnreadNotifications(10).waitForReply(true);

3
application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.script;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.benmanes.caffeine.cache.Cache;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -167,6 +168,8 @@ class TbelInvokeServiceTest extends AbstractControllerTest {
}
@Test
@Ignore("This test is based on assumption that Caffeine cache is LRU based but in fact it is based on " +
"Tiny LFU which is the cause that the tests fail sometime: https://arxiv.org/pdf/1512.00727.pdf")
public void whenCompiledScriptsCacheIsTooBig_thenRemoveRarelyUsedScripts() throws Exception {
Map<UUID, String> scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
Cache<String, Serializable> compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cache/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cluster-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

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

@ -50,4 +50,6 @@ public interface NotificationTargetService {
void deleteNotificationTargetsByTenantId(TenantId tenantId);
long countNotificationTargetsByTenantId(TenantId tenantId);
}

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

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

@ -30,6 +30,7 @@ public class CacheConstants {
public static final String TENANTS_EXIST_CACHE = "tenantsExist";
public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
public static final String NOTIFICATION_SETTINGS_CACHE = "notificationSettings";
public static final String SENT_NOTIFICATIONS_CACHE = "sentNotifications";
public static final String ASSET_PROFILE_CACHE = "assetProfiles";
public static final String ATTRIBUTES_CACHE = "attributes";

15
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java

@ -16,8 +16,10 @@
package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum NotificationRuleTriggerType {
ENTITY_ACTION,
@ -26,18 +28,15 @@ public enum NotificationRuleTriggerType {
ALARM_ASSIGNMENT,
DEVICE_ACTIVITY,
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
NEW_PLATFORM_VERSION(false),
ENTITIES_LIMIT(false),
API_USAGE_LIMIT(false);
NEW_PLATFORM_VERSION(false, true),
ENTITIES_LIMIT(false, false),
API_USAGE_LIMIT(false, false);
private final boolean tenantLevel;
NotificationRuleTriggerType(boolean tenantLevel) {
this.tenantLevel = tenantLevel;
}
private final boolean deduplicate;
NotificationRuleTriggerType() {
this(true);
this(true, false);
}
}

35
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VcUtils.java

@ -0,0 +1,35 @@
/**
* 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.sync.vc;
import org.apache.commons.lang3.StringUtils;
public class VcUtils {
private VcUtils() {}
public static void checkBranchName(String branch) {
if (StringUtils.isEmpty(branch)) return;
boolean invalid = StringUtils.containsWhitespace(branch) ||
StringUtils.containsAny(branch, "..", "~", "^", ":", "\\") ||
StringUtils.endsWithAny(branch, "/", ".lock");
if (invalid) {
throw new IllegalArgumentException("Branch name is invalid");
}
}
}

2
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>common</artifactId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/script/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/script/remote-js-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>script</artifactId>
</parent>
<groupId>org.thingsboard.common.script</groupId>

2
common/script/script-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>script</artifactId>
</parent>
<groupId>org.thingsboard.common.script</groupId>

2
common/stats/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>

2
common/transport/transport-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

4
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
@ -89,7 +89,7 @@
<artifactId>data</artifactId>
</dependency>
<dependency>
<groupId>com.github.dblock</groupId>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
</dependency>
</dependencies>

14
common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java

@ -42,7 +42,7 @@ public class SystemUtil {
long total = memory.getTotal();
long available = memory.getAvailable();
return Optional.of(toPercent(total - available, total));
} catch (Exception e) {
} catch (Throwable e) {
log.debug("Failed to get memory usage!!!", e);
}
return Optional.empty();
@ -51,7 +51,7 @@ public class SystemUtil {
public static Optional<Long> getTotalMemory() {
try {
return Optional.of(HARDWARE.getMemory().getTotal());
} catch (Exception e) {
} catch (Throwable e) {
log.debug("Failed to get total memory!!!", e);
}
return Optional.empty();
@ -59,8 +59,8 @@ public class SystemUtil {
public static Optional<Integer> getCpuUsage() {
try {
return Optional.of((int) (HARDWARE.getProcessor().getSystemCpuLoad() * 100.0));
} catch (Exception e) {
return Optional.of((int) (HARDWARE.getProcessor().getSystemCpuLoad(1000) * 100.0));
} catch (Throwable e) {
log.debug("Failed to get cpu usage!!!", e);
}
return Optional.empty();
@ -69,7 +69,7 @@ public class SystemUtil {
public static Optional<Integer> getCpuCount() {
try {
return Optional.of(HARDWARE.getProcessor().getLogicalProcessorCount());
} catch (Exception e) {
} catch (Throwable e) {
log.debug("Failed to get total cpu count!!!", e);
}
return Optional.empty();
@ -81,7 +81,7 @@ public class SystemUtil {
long total = store.getTotalSpace();
long available = store.getUsableSpace();
return Optional.of(toPercent(total - available, total));
} catch (Exception e) {
} catch (Throwable e) {
log.debug("Failed to get free disc space!!!", e);
}
return Optional.empty();
@ -91,7 +91,7 @@ public class SystemUtil {
try {
FileStore store = Files.getFileStore(Paths.get("/"));
return Optional.of(store.getTotalSpace());
} catch (Exception e) {
} catch (Throwable e) {
log.debug("Failed to get total disc space!!!", e);
}
return Optional.empty();

2
common/version-control/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>dao</artifactId>

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

@ -58,9 +58,8 @@ public class ModelConstants {
public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts";
/**
* Cassandra user constants.
* User constants.
*/
public static final String USER_COLUMN_FAMILY_NAME = "user";
public static final String USER_PG_HIBERNATE_COLUMN_FAMILY_NAME = "tb_user";
public static final String USER_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String USER_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
@ -70,12 +69,8 @@ public class ModelConstants {
public static final String USER_LAST_NAME_PROPERTY = "last_name";
public static final String USER_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String USER_BY_EMAIL_COLUMN_FAMILY_NAME = "user_by_email";
public static final String USER_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "user_by_tenant_and_search_text";
public static final String USER_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "user_by_customer_and_search_text";
/**
* Cassandra user_credentials constants.
* User_credentials constants.
*/
public static final String USER_CREDENTIALS_COLUMN_FAMILY_NAME = "user_credentials";
public static final String USER_CREDENTIALS_USER_ID_PROPERTY = USER_ID_PROPERTY;
@ -85,10 +80,6 @@ public class ModelConstants {
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";
public static final String USER_CREDENTIALS_BY_RESET_TOKEN_COLUMN_FAMILY_NAME = "user_credentials_by_reset_token";
/**
* User settings constants.
*/
@ -98,7 +89,7 @@ public class ModelConstants {
public static final String USER_SETTINGS_SETTINGS = "settings";
/**
* Cassandra admin_settings constants.
* Admin_settings constants.
*/
public static final String ADMIN_SETTINGS_COLUMN_FAMILY_NAME = "admin_settings";
@ -106,10 +97,8 @@ public class ModelConstants {
public static final String ADMIN_SETTINGS_KEY_PROPERTY = "key";
public static final String ADMIN_SETTINGS_JSON_VALUE_PROPERTY = "json_value";
public static final String ADMIN_SETTINGS_BY_KEY_COLUMN_FAMILY_NAME = "admin_settings_by_key";
/**
* Cassandra contact constants.
* Contact constants.
*/
public static final String COUNTRY_PROPERTY = "country";
public static final String STATE_PROPERTY = "state";
@ -121,7 +110,7 @@ public class ModelConstants {
public static final String EMAIL_PROPERTY = "email";
/**
* Cassandra tenant constants.
* Tenant constants.
*/
public static final String TENANT_COLUMN_FAMILY_NAME = "tenant";
public static final String TENANT_TITLE_PROPERTY = TITLE_PROPERTY;
@ -129,8 +118,6 @@ public class ModelConstants {
public static final String TENANT_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String TENANT_TENANT_PROFILE_ID_PROPERTY = "tenant_profile_id";
public static final String TENANT_BY_REGION_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "tenant_by_region_and_search_text";
/**
* Tenant profile constants.
*/
@ -139,25 +126,20 @@ public class ModelConstants {
public static final String TENANT_PROFILE_PROFILE_DATA_PROPERTY = "profile_data";
public static final String TENANT_PROFILE_DESCRIPTION_PROPERTY = "description";
public static final String TENANT_PROFILE_IS_DEFAULT_PROPERTY = "is_default";
public static final String TENANT_PROFILE_ISOLATED_TB_CORE = "isolated_tb_core";
public static final String TENANT_PROFILE_ISOLATED_TB_RULE_ENGINE = "isolated_tb_rule_engine";
/**
* Cassandra customer constants.
* Customer constants.
*/
public static final String CUSTOMER_COLUMN_FAMILY_NAME = "customer";
public static final String CUSTOMER_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String CUSTOMER_TITLE_PROPERTY = TITLE_PROPERTY;
public static final String CUSTOMER_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String CUSTOMER_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "customer_by_tenant_and_search_text";
public static final String CUSTOMER_BY_TENANT_AND_TITLE_VIEW_NAME = "customer_by_tenant_and_title";
/**
* Cassandra device constants.
* Device constants.
*/
public static final String DEVICE_COLUMN_FAMILY_NAME = "device";
public static final String DEVICE_FAMILY_NAME = "device";
public static final String DEVICE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String DEVICE_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String DEVICE_NAME_PROPERTY = "name";
@ -174,13 +156,6 @@ public class ModelConstants {
public static final String DEVICE_DEVICE_PROFILE_NAME_PROPERTY = "device_profile_name";
public static final String DEVICE_ACTIVE_PROPERTY = "active";
public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text";
public static final String DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_and_search_text";
public static final String DEVICE_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_by_type_and_search_text";
public static final String DEVICE_BY_TENANT_AND_NAME_VIEW_NAME = "device_by_tenant_and_name";
public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant";
public static final String DEVICE_INFO_VIEW_COLUMN_FAMILY_NAME = "device_info_view";
/**
@ -198,7 +173,6 @@ public class ModelConstants {
public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default";
public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id";
public static final String DEVICE_PROFILE_DEFAULT_DASHBOARD_ID_PROPERTY = "default_dashboard_id";
public static final String DEVICE_PROFILE_DEFAULT_QUEUE_ID_PROPERTY = "default_queue_id";
public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name";
public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key";
public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id";
@ -220,44 +194,28 @@ public class ModelConstants {
public static final String ASSET_PROFILE_DEFAULT_EDGE_RULE_CHAIN_ID_PROPERTY = "default_edge_rule_chain_id";
/**
* Cassandra entityView constants.
* Entity view constants.
*/
public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_view";
public static final String ENTITY_VIEW_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN;
public static final String ENTITY_VIEW_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ENTITY_VIEW_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String ENTITY_VIEW_NAME_PROPERTY = DEVICE_NAME_PROPERTY;
public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF = "entity_view_by_tenant_and_customer";
public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_TYPE_CF = "entity_view_by_tenant_and_customer_and_type";
public static final String ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF = "entity_view_by_tenant_and_entity_id";
public static final String ENTITY_VIEW_KEYS_PROPERTY = "keys";
public static final String ENTITY_VIEW_TYPE_PROPERTY = "type";
public static final String ENTITY_VIEW_START_TS_PROPERTY = "start_ts";
public static final String ENTITY_VIEW_END_TS_PROPERTY = "end_ts";
public static final String ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_CF = "entity_view_by_tenant_and_search_text";
public static final String ENTITY_VIEW_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_CF = "entity_view_by_tenant_by_type_and_search_text";
public static final String ENTITY_VIEW_BY_TENANT_AND_NAME = "entity_view_by_tenant_and_name";
/**
* Cassandra audit log constants.
* Audit log constants.
*/
public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log";
public static final String AUDIT_LOG_BY_ENTITY_ID_CF = "audit_log_by_entity_id";
public static final String AUDIT_LOG_BY_CUSTOMER_ID_CF = "audit_log_by_customer_id";
public static final String AUDIT_LOG_BY_USER_ID_CF = "audit_log_by_user_id";
public static final String AUDIT_LOG_BY_TENANT_ID_CF = "audit_log_by_tenant_id";
public static final String AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF = "audit_log_by_tenant_id_partitions";
public static final String AUDIT_LOG_ID_PROPERTY = ID_PROPERTY;
public static final String AUDIT_LOG_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String AUDIT_LOG_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String AUDIT_LOG_ENTITY_TYPE_PROPERTY = ENTITY_TYPE_PROPERTY;
public static final String AUDIT_LOG_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN;
public static final String AUDIT_LOG_ENTITY_NAME_PROPERTY = "entity_name";
public static final String AUDIT_LOG_USER_ID_PROPERTY = USER_ID_PROPERTY;
public static final String AUDIT_LOG_PARTITION_PROPERTY = "partition";
public static final String AUDIT_LOG_USER_NAME_PROPERTY = "user_name";
public static final String AUDIT_LOG_ACTION_TYPE_PROPERTY = "action_type";
public static final String AUDIT_LOG_ACTION_DATA_PROPERTY = "action_data";
@ -265,7 +223,7 @@ public class ModelConstants {
public static final String AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY = "action_failure_details";
/**
* Cassandra asset constants.
* Asset constants.
*/
public static final String ASSET_COLUMN_FAMILY_NAME = "asset";
public static final String ASSET_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
@ -277,23 +235,8 @@ public class ModelConstants {
public static final String ASSET_ASSET_PROFILE_ID_PROPERTY = "asset_profile_id";
public static final String ASSET_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_and_search_text";
public static final String ASSET_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_by_type_and_search_text";
public static final String ASSET_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_customer_and_search_text";
public static final String ASSET_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_customer_by_type_and_search_text";
public static final String ASSET_BY_TENANT_AND_NAME_VIEW_NAME = "asset_by_tenant_and_name";
public static final String ASSET_TYPES_BY_TENANT_VIEW_NAME = "asset_types_by_tenant";
/**
* Cassandra entity_subtype constants.
*/
public static final String ENTITY_SUBTYPE_COLUMN_FAMILY_NAME = "entity_subtype";
public static final String ENTITY_SUBTYPE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ENTITY_SUBTYPE_ENTITY_TYPE_PROPERTY = ENTITY_TYPE_PROPERTY;
public static final String ENTITY_SUBTYPE_TYPE_PROPERTY = "type";
/**
* Cassandra alarm constants.
* Alarm constants.
*/
public static final String ENTITY_ALARM_COLUMN_FAMILY_NAME = "entity_alarm";
public static final String ALARM_COLUMN_FAMILY_NAME = "alarm";
@ -324,12 +267,6 @@ public class ModelConstants {
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;
public static final String ALARM_COMMENT_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String ALARM_COMMENT_COLUMN_FAMILY_NAME = "alarm_comment";
public static final String ALARM_COMMENT_ALARM_ID = "alarm_id";
public static final String ALARM_COMMENT_USER_ID = USER_ID_PROPERTY;
@ -337,7 +274,7 @@ public class ModelConstants {
public static final String ALARM_COMMENT_COMMENT = "comment";
/**
* Cassandra entity relation constants.
* Entity relation constants.
*/
public static final String RELATION_COLUMN_FAMILY_NAME = "relation";
public static final String RELATION_FROM_ID_PROPERTY = "from_id";
@ -347,12 +284,8 @@ public class ModelConstants {
public static final String RELATION_TYPE_PROPERTY = "relation_type";
public static final String RELATION_TYPE_GROUP_PROPERTY = "relation_type_group";
public static final String RELATION_BY_TYPE_AND_CHILD_TYPE_VIEW_NAME = "relation_by_type_and_child_type";
public static final String RELATION_REVERSE_VIEW_NAME = "reverse_relation";
/**
* Cassandra device_credentials constants.
* Device_credentials constants.
*/
public static final String DEVICE_CREDENTIALS_COLUMN_FAMILY_NAME = "device_credentials";
public static final String DEVICE_CREDENTIALS_DEVICE_ID_PROPERTY = DEVICE_ID_PROPERTY;
@ -360,11 +293,8 @@ public class ModelConstants {
public static final String DEVICE_CREDENTIALS_CREDENTIALS_ID_PROPERTY = "credentials_id";
public static final String DEVICE_CREDENTIALS_CREDENTIALS_VALUE_PROPERTY = "credentials_value";
public static final String DEVICE_CREDENTIALS_BY_DEVICE_COLUMN_FAMILY_NAME = "device_credentials_by_device";
public static final String DEVICE_CREDENTIALS_BY_CREDENTIALS_ID_COLUMN_FAMILY_NAME = "device_credentials_by_credentials_id";
/**
* Cassandra widgets_bundle constants.
* Widgets_bundle constants.
*/
public static final String WIDGETS_BUNDLE_COLUMN_FAMILY_NAME = "widgets_bundle";
public static final String WIDGETS_BUNDLE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
@ -373,11 +303,8 @@ public class ModelConstants {
public static final String WIDGETS_BUNDLE_IMAGE_PROPERTY = "image";
public static final String WIDGETS_BUNDLE_DESCRIPTION = "description";
public static final String WIDGETS_BUNDLE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "widgets_bundle_by_tenant_and_search_text";
public static final String WIDGETS_BUNDLE_BY_TENANT_AND_ALIAS_COLUMN_FAMILY_NAME = "widgets_bundle_by_tenant_and_alias";
/**
* Cassandra widget_type constants.
* Widget_type constants.
*/
public static final String WIDGET_TYPE_COLUMN_FAMILY_NAME = "widget_type";
public static final String WIDGET_TYPE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
@ -388,10 +315,8 @@ public class ModelConstants {
public static final String WIDGET_TYPE_DESCRIPTION_PROPERTY = "description";
public static final String WIDGET_TYPE_DESCRIPTOR_PROPERTY = "descriptor";
public static final String WIDGET_TYPE_BY_TENANT_AND_ALIASES_COLUMN_FAMILY_NAME = "widget_type_by_tenant_and_aliases";
/**
* Cassandra dashboard constants.
* Dashboard constants.
*/
public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard";
public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
@ -402,10 +327,8 @@ public class ModelConstants {
public static final String DASHBOARD_MOBILE_HIDE_PROPERTY = "mobile_hide";
public static final String DASHBOARD_MOBILE_ORDER_PROPERTY = "mobile_order";
public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text";
/**
* Cassandra plugin component metadata constants.
* Plugin component metadata constants.
*/
public static final String COMPONENT_DESCRIPTOR_COLUMN_FAMILY_NAME = "component_descriptor";
public static final String COMPONENT_DESCRIPTOR_TYPE_PROPERTY = "type";
@ -416,12 +339,8 @@ public class ModelConstants {
public static final String COMPONENT_DESCRIPTOR_CONFIGURATION_DESCRIPTOR_PROPERTY = "configuration_descriptor";
public static final String COMPONENT_DESCRIPTOR_ACTIONS_PROPERTY = "actions";
public static final String COMPONENT_DESCRIPTOR_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "component_desc_by_type_search_text";
public static final String COMPONENT_DESCRIPTOR_BY_SCOPE_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "component_desc_by_scope_type_search_text";
public static final String COMPONENT_DESCRIPTOR_BY_ID = "component_desc_by_id";
/**
* Cassandra event constants.
* Event constants.
*/
public static final String ERROR_EVENT_TABLE_NAME = "error_event";
public static final String LC_EVENT_TABLE_NAME = "lc_event";
@ -432,7 +351,6 @@ public class ModelConstants {
public static final String EVENT_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String EVENT_SERVICE_ID_PROPERTY = "service_id";
public static final String EVENT_ENTITY_ID_PROPERTY = "entity_id";
public static final String EVENT_BODY_PROPERTY = "body";
public static final String EVENT_MESSAGES_PROCESSED_COLUMN_NAME = "e_messages_processed";
public static final String EVENT_ERRORS_OCCURRED_COLUMN_NAME = "e_errors_occurred";
@ -457,7 +375,7 @@ public class ModelConstants {
public static final String SINGLETON_MODE = "singleton_mode";
/**
* Cassandra rule chain constants.
* Rule chain constants.
*/
public static final String RULE_CHAIN_COLUMN_FAMILY_NAME = "rule_chain";
public static final String RULE_CHAIN_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
@ -467,11 +385,8 @@ public class ModelConstants {
public static final String RULE_CHAIN_ROOT_PROPERTY = "root";
public static final String RULE_CHAIN_CONFIGURATION_PROPERTY = "configuration";
public static final String RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "rule_chain_by_tenant_and_search_text";
public static final String RULE_CHAIN_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "rule_chain_by_tenant_by_type_and_search_text";
/**
* Cassandra rule node constants.
* Rule node constants.
*/
public static final String RULE_NODE_COLUMN_FAMILY_NAME = "rule_node";
public static final String RULE_NODE_CHAIN_ID_PROPERTY = "rule_chain_id";
@ -481,7 +396,7 @@ public class ModelConstants {
public static final String RULE_NODE_CONFIGURATION_PROPERTY = "configuration";
/**
* Rule node state constants.
* Node state constants.
*/
public static final String RULE_NODE_STATE_TABLE_NAME = "rule_node_state";
public static final String RULE_NODE_STATE_NODE_ID_PROPERTY = "rule_node_id";
@ -492,7 +407,6 @@ public class ModelConstants {
/**
* OAuth2 client registration constants.
*/
public static final String OAUTH2_PARAMS_COLUMN_FAMILY_NAME = "oauth2_params";
public static final String OAUTH2_PARAMS_ENABLED_PROPERTY = "enabled";
public static final String OAUTH2_PARAMS_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
@ -504,12 +418,8 @@ public class ModelConstants {
public static final String OAUTH2_PKG_NAME_PROPERTY = "pkg_name";
public static final String OAUTH2_APP_SECRET_PROPERTY = "app_secret";
public static final String OAUTH2_CLIENT_REGISTRATION_INFO_COLUMN_FAMILY_NAME = "oauth2_client_registration_info";
public static final String OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME = "oauth2_client_registration";
public static final String OAUTH2_CLIENT_REGISTRATION_TEMPLATE_COLUMN_FAMILY_NAME = "oauth2_client_registration_template";
public static final String OAUTH2_ENABLED_PROPERTY = "enabled";
public static final String OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY = "provider_id";
public static final String OAUTH2_CLIENT_REGISTRATION_INFO_ID_PROPERTY = "client_registration_info_id";
public static final String OAUTH2_DOMAIN_NAME_PROPERTY = "domain_name";
public static final String OAUTH2_DOMAIN_SCHEME_PROPERTY = "domain_scheme";
public static final String OAUTH2_CLIENT_ID_PROPERTY = "client_id";
@ -614,12 +524,6 @@ public class ModelConstants {
public static final String EDGE_LABEL_PROPERTY = "label";
public static final String EDGE_TYPE_PROPERTY = "type";
public static final String EDGE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String EDGE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "edge_by_tenant_and_search_text";
public static final String EDGE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "edge_by_tenant_by_type_and_search_text";
public static final String EDGE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "edge_by_customer_and_search_text";
public static final String EDGE_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "edge_by_customer_by_type_and_search_text";
public static final String EDGE_BY_TENANT_AND_NAME_VIEW_NAME = "edge_by_tenant_and_name";
public static final String EDGE_BY_ROUTING_KEY_VIEW_NAME = "edge_by_routing_key";
public static final String EDGE_ROUTING_KEY_PROPERTY = "routing_key";
public static final String EDGE_SECRET_PROPERTY = "secret";
@ -636,13 +540,11 @@ public class ModelConstants {
public static final String EDGE_EVENT_ENTITY_ID_PROPERTY = "entity_id";
public static final String EDGE_EVENT_BODY_PROPERTY = "body";
public static final String EDGE_EVENT_BY_ID_VIEW_NAME = "edge_event_by_id";
public static final String EXTERNAL_ID_PROPERTY = "external_id";
/**
* User auth settings constants.
* */
*/
public static final String USER_AUTH_SETTINGS_COLUMN_FAMILY_NAME = "user_auth_settings";
public static final String USER_AUTH_SETTINGS_USER_ID_PROPERTY = USER_ID_PROPERTY;
public static final String USER_AUTH_SETTINGS_TWO_FA_SETTINGS = "two_fa_settings";
@ -650,7 +552,6 @@ public class ModelConstants {
/**
* Cassandra attributes and timeseries constants.
*/
public static final String ATTRIBUTES_KV_CF = "attributes_kv_cf";
public static final String TS_KV_CF = "ts_kv_cf";
public static final String TS_KV_PARTITIONS_CF = "ts_kv_partitions_cf";
public static final String TS_KV_LATEST_CF = "ts_kv_latest_cf";
@ -672,7 +573,6 @@ public class ModelConstants {
/**
* Queue constants.
*/
public static final String QUEUE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String QUEUE_NAME_PROPERTY = "name";
public static final String QUEUE_TOPIC_PROPERTY = "topic";
@ -685,11 +585,9 @@ public class ModelConstants {
public static final String QUEUE_COLUMN_FAMILY_NAME = "queue";
public static final String QUEUE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
/**
* Notification constants
* */
*/
public static final String NOTIFICATION_TARGET_TABLE_NAME = "notification_target";
public static final String NOTIFICATION_TARGET_CONFIGURATION_PROPERTY = "configuration";

2
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java

@ -98,7 +98,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
defaultNotifications.create(tenantId, DefaultNotifications.apiFeatureDisabledForSysadmin, sysAdmins.getId());
defaultNotifications.create(tenantId, DefaultNotifications.apiFeatureDisabledForTenant, affectedTenantAdmins.getId());
defaultNotifications.create(tenantId, DefaultNotifications.newPlatformVersion, sysAdmins.getId(), tenantAdmins.getId());
defaultNotifications.create(tenantId, DefaultNotifications.newPlatformVersion, sysAdmins.getId());
return;
}

5
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java

@ -192,6 +192,11 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
notificationTargetDao.removeByTenantId(tenantId);
}
@Override
public long countNotificationTargetsByTenantId(TenantId tenantId) {
return notificationTargetDao.countByTenantId(tenantId);
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findNotificationTargetById(tenantId, new NotificationTargetId(entityId.getId())));

2
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java

@ -143,7 +143,7 @@ public class DefaultNotifications {
.rule(DefaultRule.builder()
.name("New platform version")
.triggerConfig(new NewPlatformVersionNotificationRuleTriggerConfig())
.description("Send notification to system admins and tenant admins when new platform version is available")
.description("Send notification to system admins when new platform version is available")
.build())
.build();

3
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java

@ -22,10 +22,11 @@ import org.thingsboard.server.common.data.notification.targets.NotificationTarge
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.TenantEntityDao;
import java.util.List;
public interface NotificationTargetDao extends Dao<NotificationTarget> {
public interface NotificationTargetDao extends Dao<NotificationTarget>, TenantEntityDao {
PageData<NotificationTarget> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);

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

@ -19,7 +19,6 @@ 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.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.dao.model.sql.AlarmEntity;
@ -316,7 +315,9 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@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")
@Query(value = "SELECT create_or_update_active_alarm(: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, :a_creation_enabled)", nativeQuery = true)
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,
@ -326,21 +327,22 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Param("a_propagate_to_tenant") boolean propagateToTenant, @Param("a_propagation_types") String propagationTypes,
@Param("a_creation_enabled") boolean alarmCreationEnabled);
@Procedure(procedureName = "update_alarm")
@Query(value = "SELECT update_alarm(:t_id, :a_id, :a_severity, :a_start_ts, :a_end_ts, :a_details, :a_propagate, :a_propagate_to_owner, " +
":a_propagate_to_tenant, :a_propagation_types)", nativeQuery = true)
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")
@Query(value = "SELECT acknowledge_alarm(:t_id, :a_id, :a_ts)", nativeQuery = true)
String acknowledgeAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long ts);
@Procedure(procedureName = "clear_alarm")
@Query(value = "SELECT clear_alarm(:t_id, :a_id, :a_ts, :a_details)", nativeQuery = true)
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")
@Query(value = "SELECT assign_alarm(:t_id, :a_id, :u_id, :a_ts)", nativeQuery = true)
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")
@Query(value = "SELECT unassign_alarm(:t_id, :a_id, :a_ts)", nativeQuery = true)
String unassignAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long unassignTime);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java

@ -71,6 +71,11 @@ public class JpaNotificationTargetDao extends JpaAbstractDao<NotificationTargetE
notificationTargetRepository.deleteByTenantId(tenantId.getId());
}
@Override
public Long countByTenantId(TenantId tenantId) {
return notificationTargetRepository.countByTenantId(tenantId.getId());
}
@Override
protected Class<NotificationTargetEntity> getEntityClass() {
return NotificationTargetEntity.class;

2
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java

@ -50,4 +50,6 @@ public interface NotificationTargetRepository extends JpaRepository<Notification
@Transactional
void deleteByTenantId(UUID tenantId);
long countByTenantId(UUID tenantId);
}

9
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java

@ -110,9 +110,12 @@ public class SqlPartitioningRepository {
String tablePartition = table + "_" + partitionTs;
String detachPsqlStmtStr = "ALTER TABLE " + table + " DETACH PARTITION " + tablePartition;
if (getCurrentServerVersion() >= PSQL_VERSION_14) {
detachPsqlStmtStr += " CONCURRENTLY";
}
// hotfix of ERROR: partition "integration_debug_event_1678323600000" already pending detach in partitioned table "public.integration_debug_event"
// https://github.com/thingsboard/thingsboard/issues/8271
// if (getCurrentServerVersion() >= PSQL_VERSION_14) {
// detachPsqlStmtStr += " CONCURRENTLY";
// }
String dropStmtStr = "DROP TABLE " + tablePartition;
try {

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

@ -859,270 +859,3 @@ CREATE TABLE IF NOT EXISTS user_settings (
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE,
CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, type)
);
DROP VIEW IF EXISTS device_info_active_attribute_view CASCADE;
CREATE OR REPLACE VIEW device_info_active_attribute_view AS
SELECT d.*
, c.title as customer_title
, COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public
, d.type as device_profile_name
, COALESCE(da.bool_v, FALSE) as active
FROM device d
LEFT JOIN customer c ON c.id = d.customer_id
LEFT JOIN attribute_kv da ON da.entity_id = d.id and da.attribute_key = 'active';
DROP VIEW IF EXISTS device_info_active_ts_view CASCADE;
CREATE OR REPLACE VIEW device_info_active_ts_view AS
SELECT d.*
, c.title as customer_title
, COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public
, d.type as device_profile_name
, COALESCE(dt.bool_v, FALSE) as active
FROM device d
LEFT JOIN customer c ON c.id = d.customer_id
LEFT JOIN ts_kv_latest dt ON dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active');
DROP VIEW IF EXISTS device_info_view CASCADE;
CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_attribute_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(NULLIF(title, ''), email) from customer where id = a.originator_id)
WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
WHEN a.originator_type = 4 THEN (select COALESCE(NULLIF(label, ''), name) from asset where id = a.originator_id)
WHEN a.originator_type = 5 THEN (select COALESCE(NULLIF(label, ''), name) from device where id = a.originator_id)
WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
WHEN a.originator_type = 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(NULLIF(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), 'old', row_to_json(existing))::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
$$;

282
dao/src/main/resources/sql/schema-views-and-functions.sql

@ -0,0 +1,282 @@
--
-- 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.
--
DROP VIEW IF EXISTS device_info_active_attribute_view CASCADE;
CREATE OR REPLACE VIEW device_info_active_attribute_view AS
SELECT d.*
, c.title as customer_title
, COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public
, d.type as device_profile_name
, COALESCE(da.bool_v, FALSE) as active
FROM device d
LEFT JOIN customer c ON c.id = d.customer_id
LEFT JOIN attribute_kv da ON da.entity_id = d.id and da.attribute_key = 'active';
DROP VIEW IF EXISTS device_info_active_ts_view CASCADE;
CREATE OR REPLACE VIEW device_info_active_ts_view AS
SELECT d.*
, c.title as customer_title
, COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public
, d.type as device_profile_name
, COALESCE(dt.bool_v, FALSE) as active
FROM device d
LEFT JOIN customer c ON c.id = d.customer_id
LEFT JOIN ts_kv_latest dt ON dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active');
DROP VIEW IF EXISTS device_info_view CASCADE;
CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_attribute_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(NULLIF(title, ''), email) from customer where id = a.originator_id)
WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
WHEN a.originator_type = 4 THEN (select COALESCE(NULLIF(label, ''), name) from asset where id = a.originator_id)
WHEN a.originator_type = 5 THEN (select COALESCE(NULLIF(label, ''), name) from device where id = a.originator_id)
WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
WHEN a.originator_type = 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(NULLIF(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), 'old', row_to_json(existing))::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
$$;

1
dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java

@ -33,6 +33,7 @@ public class PostgreSqlInitializer {
"sql/schema-entities.sql",
"sql/schema-entities-idx.sql",
"sql/schema-entities-idx-psql-addon.sql",
"sql/schema-views-and-functions.sql",
"sql/system-data.sql",
"sql/system-test-psql.sql");
private static final String dropAllTablesSqlFile = "sql/psql/drop-all-tables.sql";

1
dao/src/test/java/org/thingsboard/server/dao/TimescaleSqlInitializer.java

@ -33,6 +33,7 @@ public class TimescaleSqlInitializer {
"sql/schema-entities.sql",
"sql/schema-entities-idx.sql",
"sql/schema-entities-idx-psql-addon.sql",
"sql/schema-views-and-functions.sql",
"sql/system-data.sql",
"sql/system-test-psql.sql");
private static final String dropAllTablesSqlFile = "sql/timescale/drop-all-tables.sql";

10
dao/src/test/java/org/thingsboard/server/dao/service/install/sql/EntitiesSchemaSqlTest.java

@ -34,17 +34,21 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
public class EntitiesSchemaSqlTest extends AbstractServiceTest {
@Value("${classpath:sql/schema-entities.sql}")
private Path installScriptPath;
private Path installEntitiesPath;
@Value("${classpath:sql/schema-views-and-functions.sql}")
private Path installViewsPath;
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testRepeatedInstall() throws IOException {
String installScript = Files.readString(installScriptPath);
String entitiesScript = Files.readString(installEntitiesPath);
String viewsScript = Files.readString(installViewsPath);
try {
for (int i = 1; i <= 2; i++) {
jdbcTemplate.execute(installScript);
jdbcTemplate.execute(entitiesScript);
jdbcTemplate.execute(viewsScript);
}
} catch (Exception e) {
Assertions.fail("Failed to execute reinstall", e);

2
monitoring/pom.xml

@ -21,7 +21,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>

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

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

31
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java

@ -27,6 +27,7 @@ import io.restassured.path.json.JsonPath;
import io.restassured.response.ValidatableResponse;
import io.restassured.specification.RequestSpecification;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityView;
@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
@ -130,6 +132,18 @@ public class TestRestClient {
.as(Device.class);
}
public PageData<Device> getDevices(PageLink pageLink) {
Map<String, String> params = new HashMap<>();
addPageLinkToParam(params, pageLink);
return given().spec(requestSpec).queryParams(params)
.get("/api/tenant/devices")
.then()
.statusCode(HTTP_OK)
.extract()
.as(new TypeRef<PageData<Device>>() {
});
}
public DeviceCredentials getDeviceCredentialsByDeviceId(DeviceId deviceId) {
return given().spec(requestSpec).get("/api/device/{deviceId}/credentials", deviceId.getId())
.then()
@ -489,4 +503,21 @@ public class TestRestClient {
.then()
.statusCode(HTTP_OK);
}
public Dashboard postDashboard(Dashboard dashboard) {
return given().spec(requestSpec)
.body(dashboard)
.post("/api/dashboard")
.then()
.statusCode(HTTP_OK)
.extract()
.as(Dashboard.class);
}
public void deleteDashboard(DashboardId dashboardId) {
given().spec(requestSpec)
.delete("/api/dashboard/{dashboardId}", dashboardId.getId())
.then()
.statusCode(HTTP_OK);
}
}

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractBasePage.java

@ -185,4 +185,8 @@ abstract public class AbstractBasePage {
String s = "~`!@#$^&*()_+=-";
return s.charAt(rand.nextInt(s.length()));
}
public void pull(WebElement element, int xOffset, int yOffset) {
actions.clickAndHold(element).moveByOffset(xOffset, yOffset).release().perform();
}
}

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

@ -37,11 +37,13 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.page.PageLink;
@ -149,6 +151,12 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
.findFirst().orElse(null);
}
public Device getDeviceByName(String name) {
return testRestClient.getDevices(pageLink).getData().stream()
.filter(s -> s.getName().equals(name))
.findFirst().orElse(null);
}
public List<RuleChain> getRuleChainsByName(String name) {
return testRestClient.getRuleChains(pageLink).getData().stream()
.filter(s -> s.getName().equals(name))
@ -262,4 +270,17 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
testRestClient.deleteEntityView(entityViewId);
}
}
public void deleteDashboardById(DashboardId dashboardId) {
if (dashboardId != null) {
testRestClient.deleteDashboard(dashboardId);
}
}
public void deleteDeviceByName(String deviceName) {
Device device = getDeviceByName(deviceName);
if (device != null) {
testRestClient.deleteDevice(device.getId());
}
}
}

39
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/AlarmWidgetElements.java

@ -0,0 +1,39 @@
/**
* 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.msa.ui.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class AlarmWidgetElements extends AlarmDetailsEntityTabHelper {
public AlarmWidgetElements(WebDriver driver) {
super(driver);
}
private static final String ASSIGN_USER_DISPLAY_NAME = "//span[contains(@class,'assigned-container')]/span[contains(text(),'%s')]";
private static final String UNASSIGNED = "//span[text() = '%s']/ancestor::mat-row//span[contains(@class,'assigned-container')]" +
"//mat-icon[text() = 'account_circle']/following-sibling::span";
@Override
public WebElement assignedUser(String userEmail) {
return waitUntilElementToBeClickable(String.format(ASSIGN_USER_DISPLAY_NAME, userEmail));
}
@Override
public WebElement unassigned(String alarmType) {
return waitUntilVisibilityOfElementLocated(String.format(UNASSIGNED, alarmType));
}
}

72
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CreateWidgetPopupElements.java

@ -0,0 +1,72 @@
/**
* 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.msa.ui.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.thingsboard.server.msa.ui.base.AbstractBasePage;
public class CreateWidgetPopupElements extends AbstractBasePage {
public CreateWidgetPopupElements(WebDriver driver) {
super(driver);
}
private static final String ENTITY_ALIAS = "//input[@formcontrolname='entityAlias']";
private static final String CREATE_NEW_ALIAS_BTN = "//a[text() = 'Create a new one!']/parent::span";
private static final String FILTER_TYPE_FIELD = "//div[contains(@class,'tb-entity-filter')]//mat-select//span";
private static final String TYPE_FIELD = "//mat-select[@formcontrolname='entityType']//span";
private static final String OPTION_FROM_DROPDOWN = "//span[text() = ' %s ']";
private static final String ENTITY_FIELD = "//input[@formcontrolname='entity']";
private static final String ADD_ALIAS_BTN = "//tb-entity-alias-dialog//span[text() = ' Add ']/parent::button";
private static final String ADD_WIDGET_BTN = "//tb-add-widget-dialog//span[text() = ' Add ']/parent::button";
private static final String ENTITY_FROM_DROPDOWN = "//b[text() = '%s']";
public WebElement entityAlias() {
return waitUntilElementToBeClickable(ENTITY_ALIAS);
}
public WebElement createNewAliasBtn() {
return waitUntilElementToBeClickable(CREATE_NEW_ALIAS_BTN);
}
public WebElement filterTypeFiled() {
return waitUntilElementToBeClickable(FILTER_TYPE_FIELD);
}
public WebElement typeFiled() {
return waitUntilElementToBeClickable(TYPE_FIELD);
}
public WebElement optionFromDropdown(String type) {
return waitUntilElementToBeClickable(String.format(OPTION_FROM_DROPDOWN, type));
}
public WebElement entityFiled() {
return waitUntilElementToBeClickable(ENTITY_FIELD);
}
public WebElement addAliasBtn() {
return waitUntilElementToBeClickable(ADD_ALIAS_BTN);
}
public WebElement addWidgetBtn() {
return waitUntilElementToBeClickable(ADD_WIDGET_BTN);
}
public WebElement entityFromDropdown(String entityName) {
return waitUntilVisibilityOfElementLocated(String.format(ENTITY_FROM_DROPDOWN, entityName));
}
}

48
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CreateWidgetPopupHelper.java

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.pages;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
public class CreateWidgetPopupHelper extends CreateWidgetPopupElements {
public CreateWidgetPopupHelper(WebDriver driver) {
super(driver);
}
public void goToCreateEntityAliasPopup(String aliasName) {
entityAlias().sendKeys(aliasName + RandomStringUtils.randomAlphanumeric(7));
createNewAliasBtn().click();
}
public void selectFilterType(String filterType) {
filterTypeFiled().click();
optionFromDropdown(filterType).click();
}
public void selectType(String entityType) {
typeFiled().click();
optionFromDropdown(entityType).click();
}
public void selectEntity(String entityName) {
entityFiled().sendKeys(entityName);
entityFromDropdown(entityName);
entityFiled().sendKeys(Keys.ARROW_DOWN);
entityFiled().sendKeys(Keys.ENTER);
}
}

1
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CustomerPageHelper.java

@ -119,6 +119,7 @@ public class CustomerPageHelper extends CustomerPageElements {
public void createCustomersUser() {
plusBtn().click();
addUserEmailField().click();
addUserEmailField().sendKeys(getRandomNumber() + "@gmail.com");
addBtnC().click();
activateWindowOkBtn().click();

35
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DashboardPageElements.java

@ -30,6 +30,13 @@ public class DashboardPageElements extends OtherPageElementsHelper {
private static final String MANAGE_ASSIGNED_ENTITY_LIST_FIELD = "//input[@formcontrolname='entity']";
private static final String MANAGE_ASSIGNED_ENTITY = "//mat-option//span[contains(text(),'%s')]";
private static final String MANAGE_ASSIGNED_UPDATE_BTN = "//button[@type='submit']";
private static final String EDIT_BTN = "//mat-icon[text() = 'edit']/parent::button";
private static final String ADD_BTN = "//tb-footer-fab-buttons//button";
private static final String CREAT_NEW_DASHBOARD_BTN = "//mat-icon[text() = 'insert_drive_file']/parent::button";
private static final String ALARM_WIDGET_BUNDLE = "//mat-card-title[text() = 'Alarm widgets']/ancestor::mat-card";
private static final String ALARM_TABLE_WIDGET = "//img[@alt='Alarms table']/ancestor::mat-card";
private static final String WIDGET_SE_CORNER = "//div[contains(@class,'handle-se')]";
private static final String DONE_BTN = "//tb-footer-fab-buttons/following-sibling::button//mat-icon[text() = 'done']/parent::button";
public List<WebElement> entityTitles() {
return waitUntilVisibilityOfElementsLocated(TITLES);
@ -50,4 +57,32 @@ public class DashboardPageElements extends OtherPageElementsHelper {
public WebElement manageAssignedUpdateBtn() {
return waitUntilElementToBeClickable(MANAGE_ASSIGNED_UPDATE_BTN);
}
public WebElement editBtn() {
return waitUntilElementToBeClickable(EDIT_BTN);
}
public WebElement addBtn() {
return waitUntilElementToBeClickable(ADD_BTN);
}
public WebElement createNewDashboardBtn() {
return waitUntilElementToBeClickable(CREAT_NEW_DASHBOARD_BTN);
}
public WebElement alarmWidgetBundle() {
return waitUntilElementToBeClickable(ALARM_WIDGET_BUNDLE);
}
public WebElement alarmTableWidget() {
return waitUntilElementToBeClickable(ALARM_TABLE_WIDGET);
}
public WebElement widgetSECorner() {
return waitUntilElementToBeClickable(WIDGET_SE_CORNER);
}
public WebElement doneBtn() {
return waitUntilVisibilityOfElementLocated(DONE_BTN);
}
}

14
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DashboardPageHelper.java

@ -37,4 +37,18 @@ public class DashboardPageHelper extends DashboardPageElements {
jsClick(manageAssignedEntity(title));
jsClick(manageAssignedUpdateBtn());
}
public void openSelectWidgetsBundleMenu() {
addBtn().click();
createNewDashboardBtn().click();
}
public void openCreateWidgetPopup() {
alarmWidgetBundle().click();
alarmTableWidget().click();
}
public void increaseSizeOfTheWidget() {
pull(widgetSECorner(), 700, 200);
}
}

117
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java

@ -18,7 +18,7 @@ package org.thingsboard.server.msa.ui.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class DevicePageElements extends OtherPageElements {
public class DevicePageElements extends OtherPageElementsHelper {
public DevicePageElements(WebDriver driver) {
super(driver);
}
@ -29,9 +29,30 @@ public class DevicePageElements extends OtherPageElements {
private static final String ASSIGN_TO_CUSTOMER_BTN = "//mat-cell[contains(@class,'name')]/span[text()='%s']" +
"/ancestor::mat-row//mat-icon[contains(text(),'assignment_ind')]/parent::button";
private static final String CHOOSE_CUSTOMER_FOR_ASSIGN_FIELD = "//input[@formcontrolname='entity']";
private static final String CUSTOMER_FROM_ASSIGN_DROPDOWN = "//div[@role = 'listbox']//span[text() = '%s']";
private static final String ENTITY_FROM_DROPDOWN = "//div[@role = 'listbox']//span[text() = '%s']";
private static final String CLOSE_DEVICE_DETAILS_VIEW = "//header//mat-icon[contains(text(),'close')]/parent::button";
private static final String SUBMIT_ASSIGN_TO_CUSTOMER_BTN = "//button[@type='submit']";
private static final String ADD_DEVICE_BTN = "//mat-icon[text() = 'insert_drive_file']/parent::button";
private static final String CREATE_DEVICE_NAME_FIELD = "//tb-device-wizard//input[@formcontrolname='name']";
private static final String HEADER_NAME_VIEW = "//header//div[@class='tb-details-title']/span";
private static final String DESCRIPTION_FIELD_CREATE_VIEW = "//tb-device-wizard//textarea[@formcontrolname='description']";
private static final String ADD_DEVICE_VIEW = "//tb-device-wizard";
private static final String DELETE_BTN_DETAILS_TAB = "//span[contains(text(),'Delete device')]/parent::button";
private static final String CHECKBOX_GATEWAY_EDIT = "//mat-checkbox[@formcontrolname='gateway']//label";
private static final String CHECKBOX_GATEWAY_CREATE = "//tb-device-wizard//mat-checkbox[@formcontrolname='gateway']//label";
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT = "//mat-checkbox[@formcontrolname='overwriteActivityTime']//label";
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_CREATE = "//tb-device-wizard//mat-checkbox[@formcontrolname='overwriteActivityTime']//label";
private static final String CHECKBOX_GATEWAY_DETAILS = "//mat-checkbox[@formcontrolname='gateway']//input";
private static final String CHECKBOX_GATEWAY_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-gateway')]//mat-icon[text() = 'check_box']";
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_DETAILS = "//mat-checkbox[@formcontrolname='overwriteActivityTime']//input";
private static final String CLEAR_PROFILE_FIELD_BTN = "//button[@aria-label='Clear']";
private static final String DEVICE_PROFILE_REDIRECTED_BTN = "//a[@aria-label='Open device profile']";
private static final String DEVICE_LABEL_FIELD_CREATE = "//tb-device-wizard//input[@formcontrolname='label']";
private static final String DEVICE_LABEL_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-label')]/span";
private static final String DEVICE_CUSTOMER_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-customerTitle')]/span";
private static final String CUSTOMER_OPTION_BNT = "//div[text() = 'Customer']/ancestor::mat-step-header";
private static final String ASSIGN_ON_CUSTOMER_FIELD = "//input[@formcontrolname='entity']";
private static final String DEVICE_LABEL_EDIT = "//input[@formcontrolname='label']";
public WebElement device(String deviceName) {
return waitUntilElementToBeClickable(String.format(DEVICE, deviceName));
@ -53,8 +74,8 @@ public class DevicePageElements extends OtherPageElements {
return waitUntilElementToBeClickable(CHOOSE_CUSTOMER_FOR_ASSIGN_FIELD);
}
public WebElement customerFromAssignDropdown(String customerTitle) {
return waitUntilElementToBeClickable(String.format(CUSTOMER_FROM_ASSIGN_DROPDOWN, customerTitle));
public WebElement entityFromDropdown(String customerTitle) {
return waitUntilElementToBeClickable(String.format(ENTITY_FROM_DROPDOWN, customerTitle));
}
public WebElement closeDeviceDetailsViewBtn() {
@ -64,4 +85,92 @@ public class DevicePageElements extends OtherPageElements {
public WebElement submitAssignToCustomerBtn() {
return waitUntilElementToBeClickable(SUBMIT_ASSIGN_TO_CUSTOMER_BTN);
}
public WebElement addDeviceBtn() {
return waitUntilElementToBeClickable(ADD_DEVICE_BTN);
}
public WebElement nameField() {
return waitUntilElementToBeClickable(CREATE_DEVICE_NAME_FIELD);
}
public WebElement headerNameView() {
return waitUntilVisibilityOfElementLocated(HEADER_NAME_VIEW);
}
public WebElement descriptionFieldCreateField() {
return waitUntilElementToBeClickable(DESCRIPTION_FIELD_CREATE_VIEW);
}
public WebElement addDeviceView() {
return waitUntilPresenceOfElementLocated(ADD_DEVICE_VIEW);
}
public WebElement deleteBtnDetailsTab() {
return waitUntilElementToBeClickable(DELETE_BTN_DETAILS_TAB);
}
public WebElement checkboxGatewayEdit() {
return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_EDIT);
}
public WebElement checkboxGatewayCreate() {
return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_CREATE);
}
public WebElement checkboxOverwriteActivityTimeEdit() {
return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT);
}
public WebElement checkboxOverwriteActivityTimeCreate() {
return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_CREATE);
}
public WebElement checkboxGatewayDetailsTab() {
return waitUntilPresenceOfElementLocated(CHECKBOX_GATEWAY_DETAILS);
}
public WebElement checkboxGatewayPage(String deviceName) {
return waitUntilPresenceOfElementLocated(String.format(CHECKBOX_GATEWAY_PAGE, deviceName));
}
public WebElement checkboxOverwriteActivityTimeDetails() {
return waitUntilPresenceOfElementLocated(CHECKBOX_OVERWRITE_ACTIVITY_TIME_DETAILS);
}
public WebElement clearProfileFieldBtn() {
return waitUntilElementToBeClickable(CLEAR_PROFILE_FIELD_BTN);
}
public WebElement deviceProfileRedirectedBtn() {
return waitUntilElementToBeClickable(DEVICE_PROFILE_REDIRECTED_BTN);
}
public WebElement deviceLabelFieldCreate() {
return waitUntilElementToBeClickable(DEVICE_LABEL_FIELD_CREATE);
}
public WebElement deviceLabelOnPage(String deviceName) {
return waitUntilVisibilityOfElementLocated(String.format(DEVICE_LABEL_PAGE, deviceName));
}
public WebElement customerOptionBtn() {
return waitUntilElementToBeClickable(CUSTOMER_OPTION_BNT);
}
public WebElement assignOnCustomerField() {
return waitUntilElementToBeClickable(ASSIGN_ON_CUSTOMER_FIELD);
}
public WebElement deviceCustomerOnPage(String deviceName) {
return waitUntilVisibilityOfElementLocated(String.format(DEVICE_CUSTOMER_PAGE, deviceName));
}
public WebElement deviceLabelEditField() {
return waitUntilElementToBeClickable(DEVICE_LABEL_EDIT);
}
public WebElement deviceLabelDetailsField() {
return waitUntilVisibilityOfElementLocated(DEVICE_LABEL_EDIT);
}
}

61
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java

@ -22,6 +22,9 @@ public class DevicePageHelper extends DevicePageElements {
super(driver);
}
private String description;
private String label;
public void openDeviceAlarms(String deviceName) {
if (!deviceDetailsView().isDisplayed()) {
device(deviceName).click();
@ -31,7 +34,63 @@ public class DevicePageHelper extends DevicePageElements {
public void assignToCustomer(String customerTitle) {
chooseCustomerForAssignField().click();
customerFromAssignDropdown(customerTitle).click();
entityFromDropdown(customerTitle).click();
submitAssignToCustomerBtn().click();
}
public void openCreateDeviceView() {
plusBtn().click();
addDeviceBtn().click();
}
public void enterName(String deviceName) {
enterText(nameField(), deviceName);
}
public void enterDescription(String description) {
enterText(descriptionFieldCreateField(), description);
}
public void enterLabel(String label) {
enterText(deviceLabelFieldCreate(), label);
}
public void deleteDeviceByRightSideBtn(String deviceName) {
deleteBtn(deviceName).click();
warningPopUpYesBtn().click();
}
public void deleteDeviceFromDetailsTab() {
deleteBtnDetailsTab().click();
warningPopUpYesBtn().click();
}
public void setDescription() {
scrollToElement(descriptionEntityView());
description = descriptionEntityView().getAttribute("value");
}
public void setLabel() {
label = deviceLabelDetailsField().getAttribute("value");
}
public String getDescription() {
return description;
}
public String getLabel() {
return label;
}
public void changeDeviceProfile(String deviceProfileName) {
clearProfileFieldBtn().click();
entityFromDropdown(deviceProfileName).click();
}
public void assignOnCustomer(String customerTitle) {
customerOptionBtn().click();
assignOnCustomerField().click();
entityFromList(customerTitle).click();
sleep(2); //waiting for the action to count
}
}

6
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/SideBarMenuViewElements.java

@ -27,7 +27,7 @@ public class SideBarMenuViewElements extends AbstractBasePage {
private static final String RULE_CHAINS_BTN = "//mat-toolbar//a[@href='/ruleChains']";
private static final String CUSTOMER_BTN = "//mat-toolbar//a[@href='/customers']";
private static final String DASHBOARD_BTN = "//mat-toolbar//a[@href='/dashboards']";
private static final String PROFILES_BTN = "//mat-toolbar//mat-icon[text()='badge']/ancestor::tb-menu-toggle";
private static final String PROFILES_DROPDOWN = "//mat-toolbar//mat-icon[text()='badge']/ancestor::a//span[contains(@class,'pull-right')]";
private static final String DEVICE_PROFILE_BTN = "//mat-toolbar//a[@href='/profiles/deviceProfiles']";
private static final String ASSET_PROFILE_BTN = "//mat-toolbar//a[@href='/profiles/assetProfiles']";
private static final String ALARMS_BTN = "//mat-toolbar//a[@href='/alarms']";
@ -52,8 +52,8 @@ public class SideBarMenuViewElements extends AbstractBasePage {
return waitUntilElementToBeClickable(DASHBOARD_BTN);
}
public WebElement profilesBtn() {
return waitUntilElementToBeClickable(PROFILES_BTN);
public WebElement profilesDropdown() {
return waitUntilElementToBeClickable(PROFILES_DROPDOWN);
}
public WebElement deviceProfileBtn() {

14
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/SideBarMenuViewHelper.java

@ -24,12 +24,12 @@ public class SideBarMenuViewHelper extends SideBarMenuViewElements {
}
public void openDeviceProfiles() {
profilesBtn().click();
openProfilesDropDown();
deviceProfileBtn().click();
}
public void openAssetProfiles() {
profilesBtn().click();
openProfilesDropDown();
assetProfileBtn().click();
}
@ -54,10 +54,20 @@ public class SideBarMenuViewHelper extends SideBarMenuViewElements {
}
}
public void openProfilesDropDown() {
if (profilesIsClose()) {
profilesDropdown().click();
}
}
public boolean entitiesDropdownIsClose() {
return dropdownIsClose(entitiesDropdown());
}
public boolean profilesIsClose() {
return dropdownIsClose(profilesDropdown());
}
private boolean dropdownIsClose(WebElement dropdown) {
return !dropdown.getAttribute("class").contains("tb-toggled");
}

146
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assignee/AssignFromAlarmWidgetTest.java

@ -0,0 +1,146 @@
/**
* 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.msa.ui.tests.assignee;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.msa.ui.pages.AlarmWidgetElements;
import org.thingsboard.server.msa.ui.pages.CreateWidgetPopupHelper;
import org.thingsboard.server.msa.ui.pages.DashboardPageHelper;
import org.thingsboard.server.msa.ui.utils.Const;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import static org.assertj.core.api.Assertions.assertThat;
@Feature("Assign from details tab of entity")
public class AssignFromAlarmWidgetTest extends AbstractAssignTest {
private Dashboard dashboard;
private DashboardPageHelper dashboardPage;
private AlarmWidgetElements alarmWidget;
@BeforeClass
public void create() {
dashboardPage = new DashboardPageHelper(driver);
CreateWidgetPopupHelper createWidgetPopup = new CreateWidgetPopupHelper(driver);
alarmWidget = new AlarmWidgetElements(driver);
dashboard = testRestClient.postDashboard(EntityPrototypes.defaultDashboardPrototype("Dashboard"));
sideBarMenuView.dashboardBtn().click();
dashboardPage.entity(dashboard.getName()).click();
dashboardPage.editBtn().click();
dashboardPage.openSelectWidgetsBundleMenu();
dashboardPage.openCreateWidgetPopup();
createWidgetPopup.goToCreateEntityAliasPopup("Alias");
createWidgetPopup.selectFilterType("Single entity");
createWidgetPopup.selectType("Device");
createWidgetPopup.selectEntity(deviceName);
createWidgetPopup.addAliasBtn().click();
createWidgetPopup.addWidgetBtn().click();
dashboardPage.increaseSizeOfTheWidget();
dashboardPage.doneBtn().click();
}
@AfterClass
public void delete() {
deleteDashboardById(dashboard.getId());
}
@BeforeMethod
public void goToDashboardPage() {
sideBarMenuView.dashboardBtn().click();
dashboardPage.entity(dashboard.getName()).click();
}
@Description("Can assign alarm to yourself")
@Test
public void assignAlarmToYourself() {
alarmWidget.assignAlarmTo(alarmType, Const.TENANT_EMAIL);
assertIsDisplayed(alarmWidget.assignedUser(Const.TENANT_EMAIL));
}
@Description("Can reassign alarm to another user")
@Test
public void reassignAlarm() {
alarmWidget.assignAlarmTo(assignedAlarmType, userWithNameEmail);
assertIsDisplayed(alarmWidget.assignedUser(userName));
}
@Description("Can unassign alarm")
@Test
public void unassignedAlarm() {
alarmWidget.unassignedAlarm(assignedAlarmType);
assertIsDisplayed(alarmWidget.unassigned(assignedAlarmType));
}
@Description("Assign alarm to yourself from details of alarm")
@Test
public void assignAlarmToYourselfFromDetails() {
alarmWidget.alarmDetailsBtn(alarmType).click();
alarmDetailsView.assignAlarmTo(Const.TENANT_EMAIL);
alarmDetailsView.closeAlarmDetailsViewBtn().click();
assertIsDisplayed(alarmWidget.assignedUser(Const.TENANT_EMAIL));
}
@Description("Reassign alarm to another user from details of alarm")
@Test
public void reassignAlarmFromDetails() {
alarmWidget.alarmDetailsBtn(assignedAlarmType).click();
alarmDetailsView.assignAlarmTo(userWithNameEmail);
alarmDetailsView.closeAlarmDetailsViewBtn().click();
assertIsDisplayed(alarmWidget.assignedUser(userName));
}
@Description("Unassign alarm from details of alarm")
@Test
public void unassignedAlarmFromDetails() {
alarmWidget.alarmDetailsBtn(assignedAlarmType).click();
alarmDetailsView.unassignedAlarm();
alarmDetailsView.closeAlarmDetailsViewBtn().click();
assertIsDisplayed(alarmWidget.unassigned(assignedAlarmType));
}
@Description("Search by email")
@Test
public void searchByEmail() {
alarmWidget.searchAlarm(alarmType, Const.TENANT_EMAIL);
alarmWidget.setUsers();
assertThat(alarmWidget.getUsers()).hasSize(1).as("Search result contains search input").contains(Const.TENANT_EMAIL);
alarmWidget.assignUsers().forEach(this::assertIsDisplayed);
}
@Description("Search by name")
@Test(groups = "broken")
public void searchByName() {
alarmWidget.searchAlarm(alarmType, userName);
alarmWidget.setUsers();
assertThat(alarmWidget.getUsers()).hasSize(1).as("Search result contains search input").contains(userName);
alarmWidget.assignUsers().forEach(this::assertIsDisplayed);
}
}

45
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.tests.devicessmoke;
import io.qameta.allure.Epic;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.DevicePageHelper;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewHelper;
@Epic("Device smoke tests")
abstract public class AbstractDeviceTest extends AbstractDriverBaseTest {
protected SideBarMenuViewHelper sideBarMenuView;
protected DevicePageHelper devicePage;
protected String deviceName;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver);
devicePage = new DevicePageHelper(driver);
}
@AfterMethod
public void delete() {
deleteDeviceByName(deviceName);
deviceName = null;
}
}

214
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java

@ -0,0 +1,214 @@
/**
* 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.msa.ui.tests.devicessmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import org.testng.annotations.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random;
import static org.thingsboard.server.msa.ui.utils.Const.DEVICE_PROFILE_IS_REQUIRED_MESSAGE;
import static org.thingsboard.server.msa.ui.utils.Const.EMPTY_DEVICE_MESSAGE;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
import static org.thingsboard.server.msa.ui.utils.Const.NAME_IS_REQUIRED_MESSAGE;
import static org.thingsboard.server.msa.ui.utils.Const.SAME_NAME_WARNING_DEVICE_MESSAGE;
@Feature("Create device")
public class CreateDeviceTest extends AbstractDeviceTest {
@Test(groups = "smoke")
@Description("Add device after specifying the name (text/numbers /special characters)")
public void createDevice() {
deviceName = ENTITY_NAME + random();
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.addBtnC().click();
devicePage.refreshBtn().click();
assertIsDisplayed(devicePage.entity(deviceName));
}
@Test(groups = "smoke")
@Description("Add device after specifying the name and description (text/numbers /special characters)")
public void createDeviceWithDescription() {
deviceName = ENTITY_NAME + random();
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.enterDescription(deviceName);
devicePage.addBtnC().click();
devicePage.refreshBtn().click();
devicePage.entity(deviceName).click();
devicePage.setHeaderName();
assertThat(devicePage.getHeaderName()).as("Header of device details tab").isEqualTo(deviceName);
assertThat(devicePage.descriptionEntityView().getAttribute("value"))
.as("Description in device details tab").isEqualTo(deviceName);
}
@Test(groups = "smoke")
@Description("Add device without the name")
public void createDeviceWithoutName() {
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.nameField().click();
devicePage.addBtnC().click();
assertIsDisplayed(devicePage.addDeviceView());
assertThat(devicePage.errorMessage().getText()).as("Text of warning message").isEqualTo(NAME_IS_REQUIRED_MESSAGE);
}
@Test(groups = "smoke")
@Description("Create device only with spase in name")
public void createDeviceWithOnlySpace() {
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(" ");
devicePage.addBtnC().click();
assertIsDisplayed(devicePage.warningMessage());
assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(EMPTY_DEVICE_MESSAGE);
assertIsDisplayed(devicePage.addDeviceView());
}
@Test(groups = "smoke")
@Description("Create a device with the same name")
public void createDeviceWithSameName() {
Device device = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME));
deviceName = device.getName();
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.addBtnC().click();
assertIsDisplayed(devicePage.warningMessage());
assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(SAME_NAME_WARNING_DEVICE_MESSAGE);
assertIsDisplayed(devicePage.addDeviceView());
}
@Test(groups = "smoke")
@Description("Add device after specifying the name (text/numbers /special characters) without refresh")
public void createDeviceWithoutRefresh() {
deviceName = ENTITY_NAME + random();
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.addBtnC().click();
assertIsDisplayed(devicePage.entity(deviceName));
}
@Test(groups = "smoke")
@Description("Add device without device profile")
public void createDeviceWithoutDeviceProfile() {
deviceName = ENTITY_NAME + random();
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.clearProfileFieldBtn().click();
devicePage.addBtnC().click();
assertIsDisplayed(devicePage.errorMessage());
assertThat(devicePage.errorMessage().getText()).as("Text of warning message").isEqualTo(DEVICE_PROFILE_IS_REQUIRED_MESSAGE);
assertIsDisplayed(devicePage.addDeviceView());
}
@Test(groups = "smoke")
@Description("Add device with enabled gateway")
public void createDeviceWithEnableGateway() {
deviceName = ENTITY_NAME + random();
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.checkboxGatewayCreate().click();
devicePage.addBtnC().click();
assertIsDisplayed(devicePage.device(deviceName));
assertIsDisplayed(devicePage.checkboxGatewayPage(deviceName));
}
@Test(groups = "smoke")
@Description("Add device with enabled overwrite activity time for connected")
public void createDeviceWithEnableOverwriteActivityTimeForConnected() {
deviceName = ENTITY_NAME + random();
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.checkboxGatewayCreate().click();
devicePage.checkboxOverwriteActivityTimeCreate().click();
devicePage.addBtnC().click();
devicePage.device(deviceName).click();
assertThat(devicePage.checkboxOverwriteActivityTimeDetails().getAttribute("class").contains("selected"))
.as("Overwrite activity time for connected is enable").isTrue();
}
@Test(groups = "smoke")
@Description("Add device with label")
public void createDeviceWithLabel() {
deviceName = ENTITY_NAME + random();
String deviceLabel = "device label " + random();
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.enterLabel(deviceLabel);
devicePage.addBtnC().click();
assertIsDisplayed(devicePage.deviceLabelOnPage(deviceName));
assertThat(devicePage.deviceLabelOnPage(deviceName).getText()).as("Label added correctly").isEqualTo(deviceLabel);
}
@Test(groups = "smoke")
@Description("Add device with assignee on customer")
public void createDeviceWithAssignee() {
deviceName = ENTITY_NAME + random();
String customer = "Customer A";
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.assignOnCustomer(customer);
devicePage.addBtnC().click();
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
.as("Customer added correctly").isEqualTo(customer);
}
@Test(groups = "smoke")
@Description("Go to devices documentation page")
public void documentation() {
String urlPath = "docs/user-guide/ui/devices/";
sideBarMenuView.goToDevicesPage();
devicePage.entity("Thermostat T1").click();
devicePage.goToHelpPage();
assertThat(urlContains(urlPath)).as("Redirected URL contains " + urlPath).isTrue();
}
}

75
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeleteDeviceTest.java

@ -0,0 +1,75 @@
/**
* 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.msa.ui.tests.devicessmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
@Feature("Delete device")
public class DeleteDeviceTest extends AbstractDeviceTest {
@BeforeMethod
public void createDevice() {
Device device = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME));
deviceName = device.getName();
}
@Test(groups = "smoke")
@Description("Remove the device by clicking on the trash icon in the right side of device")
public void deleteDeviceByRightSideBtn() {
sideBarMenuView.goToDevicesPage();
devicePage.deleteDeviceByRightSideBtn(deviceName);
devicePage.refreshBtn().click();
devicePage.assertEntityIsNotPresent(deviceName);
}
@Test(groups = "smoke")
@Description("Remove device by mark in the checkbox and then click on the trash can icon in the menu that appears at the top")
public void deleteSelectedDevice() {
sideBarMenuView.goToDevicesPage();
devicePage.deleteSelected(deviceName);
devicePage.refreshBtn().click();
devicePage.assertEntityIsNotPresent(deviceName);
}
@Test(groups = "smoke")
@Description("Remove the device by clicking on the 'Delete device' btn in the entity view")
public void deleteDeviceFromDetailsTab() {
sideBarMenuView.goToDevicesPage();
devicePage.entity(deviceName).click();
devicePage.deleteDeviceFromDetailsTab();
devicePage.refreshBtn();
devicePage.assertEntityIsNotPresent(deviceName);
}
@Test(groups = "smoke")
@Description("Remove the device by clicking on the trash icon in the right side of device without refresh")
public void deleteDeviceWithoutRefresh() {
sideBarMenuView.goToDevicesPage();
devicePage.deleteDeviceByRightSideBtn(deviceName);
devicePage.assertEntityIsNotPresent(deviceName);
}
}

84
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeleteSeveralDevicesTest.java

@ -0,0 +1,84 @@
/**
* 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.msa.ui.tests.devicessmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
@Feature("Delete several devices")
public class DeleteSeveralDevicesTest extends AbstractDeviceTest {
private String deviceName1;
private String deviceName2;
@BeforeMethod
public void createDevices() {
Device device = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME));
Device device1 = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME));
deviceName1 = device.getName();
deviceName2 = device1.getName();
}
@AfterMethod
public void deleteDevices() {
deleteDeviceByName(deviceName1);
deleteDeviceByName(deviceName2);
}
@Test(groups = "smoke")
@Description("Remove several devices by mark in the checkbox and then click on the trash can icon in the menu " +
"that appears at the top")
public void deleteSeveralDevicesByTopBtn() {
sideBarMenuView.goToDevicesPage();
devicePage.deleteSelected(2);
devicePage.refreshBtn().click();
devicePage.assertEntityIsNotPresent(deviceName1);
devicePage.assertEntityIsNotPresent(deviceName2);
}
@Test(groups = "smoke")
@Description("Remove several devices by mark all the devices on the page by clicking in the topmost checkbox" +
" and then clicking on the trash icon in the menu that appears")
public void selectAllDevices() {
sideBarMenuView.goToDevicesPage();
devicePage.selectAllCheckBox().click();
devicePage.deleteSelectedBtn().click();
assertIsDisplayed(devicePage.warningPopUpTitle());
assertThat(devicePage.warningPopUpTitle().getText()).as("Warning title contains true correct of selected devices")
.contains(String.valueOf(devicePage.markCheckbox().size()));
}
@Test(groups = "smoke")
@Description("Remove several devices by mark in the checkbox and then click on the trash can icon in the menu " +
"that appears at the top without refresh")
public void deleteSeveralWithoutRefresh() {
sideBarMenuView.goToDevicesPage();
devicePage.deleteSelected(2);
devicePage.assertEntityIsNotPresent(deviceName1);
devicePage.assertEntityIsNotPresent(deviceName2);
}
}

179
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/EditDeviceTest.java

@ -0,0 +1,179 @@
/**
* 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.msa.ui.tests.devicessmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.utils.DataProviderCredential;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.getRandomNumber;
import static org.thingsboard.server.msa.ui.utils.Const.EMPTY_DEVICE_MESSAGE;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
@Feature("Edit device")
public class EditDeviceTest extends AbstractDeviceTest {
@Test(groups = "smoke")
@Description("Change name by edit menu")
public void changeName() {
String newDeviceName = "Changed" + getRandomNumber();
deviceName = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME)).getName();
sideBarMenuView.goToDevicesPage();
devicePage.entity(deviceName).click();
devicePage.setHeaderName();
String nameBefore = devicePage.getHeaderName();
devicePage.editPencilBtn().click();
devicePage.changeNameEditMenu(newDeviceName);
devicePage.doneBtnEditView().click();
deviceName = newDeviceName;
devicePage.setHeaderName();
String nameAfter = devicePage.getHeaderName();
assertThat(nameAfter).as("The name has changed").isNotEqualTo(nameBefore);
assertThat(nameAfter).as("The name has changed correctly").isEqualTo(newDeviceName);
}
@Test(groups = "smoke")
@Description("Delete name and save")
public void deleteName() {
deviceName = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME)).getName();
sideBarMenuView.goToDevicesPage();
devicePage.entity(deviceName).click();
devicePage.editPencilBtn().click();
devicePage.changeNameEditMenu("");
assertIsDisable(devicePage.doneBtnEditViewVisible());
}
@Test(groups = "smoke")
@Description("Save only with space")
public void saveOnlyWithSpace() {
deviceName = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME)).getName();
sideBarMenuView.goToDevicesPage();
devicePage.entity(deviceName).click();
devicePage.editPencilBtn().click();
devicePage.changeNameEditMenu(" ");
devicePage.doneBtnEditView().click();
assertIsDisplayed(devicePage.warningMessage());
assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(EMPTY_DEVICE_MESSAGE);
}
@Test(groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "editMenuDescription")
@Description("Write the description and save the changes/Change the description and save the changes/Delete the description and save the changes")
public void editDescription(String description, String newDescription, String finalDescription) {
deviceName = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME, description)).getName();
sideBarMenuView.goToDevicesPage();
devicePage.entity(deviceName).click();
devicePage.editPencilBtn().click();
devicePage.descriptionEntityView().sendKeys(newDescription);
devicePage.doneBtnEditView().click();
devicePage.setDescription();
assertThat(devicePage.getDescription()).as("The description changed correctly").isEqualTo(finalDescription);
}
@Test(groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "enable")
@Description("Enable gateway mode/Disable gateway")
public void isGateway(boolean isGateway) {
deviceName = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME, isGateway)).getName();
sideBarMenuView.goToDevicesPage();
devicePage.entity(deviceName).click();
devicePage.editPencilBtn().click();
devicePage.checkboxGatewayEdit().click();
devicePage.doneBtnEditView().click();
if (isGateway) {
assertThat(devicePage.checkboxGatewayDetailsTab().getAttribute("class").contains("selected"))
.as("Gateway is disable").isFalse();
} else {
assertThat(devicePage.checkboxGatewayDetailsTab().getAttribute("class").contains("selected"))
.as("Gateway is enable").isTrue();
}
}
@Test(groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "enable")
@Description("Enable overwrite activity time for connected/Disable overwrite activity time for connected")
public void isOverwriteActivityTimeForConnectedDevice(boolean isOverwriteActivityTimeForConnected) {
deviceName = testRestClient.postDevice("",
EntityPrototypes.defaultDevicePrototype(ENTITY_NAME, true, isOverwriteActivityTimeForConnected)).getName();
sideBarMenuView.goToDevicesPage();
devicePage.entity(deviceName).click();
devicePage.editPencilBtn().click();
devicePage.checkboxOverwriteActivityTimeEdit().click();
devicePage.doneBtnEditView().click();
if (isOverwriteActivityTimeForConnected) {
assertThat(devicePage.checkboxOverwriteActivityTimeDetails().getAttribute("class").contains("selected"))
.as("Overwrite activity time for connected is disable").isFalse();
} else {
assertThat(devicePage.checkboxOverwriteActivityTimeDetails().getAttribute("class").contains("selected"))
.as("Overwrite activity time for connected is enable").isTrue();
}
}
@Test(groups = "smoke")
@Description("Change device profile")
public void changeDeviceProfile() {
deviceName = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME)).getName();
sideBarMenuView.goToDevicesPage();
devicePage.entity(deviceName).click();
devicePage.editPencilBtn().click();
devicePage.changeDeviceProfile("DEFAULT");
devicePage.doneBtnEditView().click();
assertIsDisplayed(devicePage.deviceProfileRedirectedBtn());
assertThat(devicePage.deviceProfileRedirectedBtn().getText()).as("Profile changed correctly").isEqualTo("DEFAULT");
}
@Test(groups = "smoke")
@Description("Save without device profile")
public void saveWithoutDeviceProfile() {
deviceName = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME)).getName();
sideBarMenuView.goToDevicesPage();
devicePage.entity(deviceName).click();
devicePage.editPencilBtn().click();
devicePage.clearProfileFieldBtn().click();
assertIsDisable(devicePage.doneBtnEditViewVisible());
}
@Test(groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "editDeviceLabel")
@Description("Write the label and save the changes/Change the label and save the changes/Delete the label and save the changes")
public void editLabel(String label, String newLabel, String finalLabel) {
deviceName = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME, "", label)).getName();
sideBarMenuView.goToDevicesPage();
devicePage.entity(deviceName).click();
devicePage.editPencilBtn().click();
devicePage.deviceLabelEditField().sendKeys(newLabel);
devicePage.doneBtnEditView().click();
devicePage.setLabel();
assertThat(devicePage.getLabel()).as("The label changed correctly").isEqualTo(finalLabel);
}
}

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/RuleChainEditMenuTest.java

@ -99,7 +99,7 @@ public class RuleChainEditMenuTest extends AbstractRuleChainTest {
assertThat(ruleChainsPage.getDescription()).as("The description changed correctly").isEqualTo(finalDescription);
}
@Test(priority = 20, groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "debugMode")
@Test(priority = 20, groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "enable")
@Description("Enable debug mode/Disable debug mode")
public void debugMode(boolean debugMode) {
ruleChainName = ENTITY_NAME + random();

5
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/Const.java

@ -36,10 +36,13 @@ public class Const {
public static final String EMPTY_CUSTOMER_MESSAGE = "Customer title should be specified!";
public static final String EMPTY_DEVICE_PROFILE_MESSAGE = "Device profile name should be specified!";
public static final String EMPTY_ASSET_PROFILE_MESSAGE = "Asset profile name should be specified!";
public static final String EMPTY_DEVICE_MESSAGE = "Device name should be specified!";
public static final String DELETE_RULE_CHAIN_WITH_PROFILE_MESSAGE = "The rule chain referenced by the device profiles cannot be deleted!";
public static final String SAME_NAME_WARNING_CUSTOMER_MESSAGE = "Customer with such title already exists!";
public static final String SAME_NAME_WARNING_DEVICE_PROFILE_MESSAGE = "Device profile with such name already exists!";
public static final String SAME_NAME_WARNING_ASSET_PROFILE_MESSAGE = "Asset profile with such name already exists!";
public static final String SAME_NAME_WARNING_DEVICE_MESSAGE = "Device with such name already exists!";
public static final String PHONE_NUMBER_ERROR_MESSAGE = "Phone number is invalid or not possible";
public static final String NAME_IS_REQUIRED_MESSAGE = "Name is required.";
}
public static final String DEVICE_PROFILE_IS_REQUIRED_MESSAGE = "Device profile is required";
}

12
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/DataProviderCredential.java

@ -151,9 +151,19 @@ public class DataProviderCredential {
}
@DataProvider
public static Object[][] debugMode() {
public static Object[][] enable() {
return new Object[][]{
{false},
{true}};
}
@DataProvider
public static Object[][] editDeviceLabel() {
String newLabel = "Label" + getRandomNumber();
String label = "Label";
return new Object[][]{
{"", newLabel, newLabel},
{label, newLabel, label + newLabel},
{label, Keys.CONTROL + "A" + Keys.BACK_SPACE, ""}};
}
}

42
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/EntityPrototypes.java

@ -18,6 +18,7 @@ package org.thingsboard.server.msa.ui.utils;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
@ -191,6 +192,41 @@ public class EntityPrototypes {
return device;
}
public static Device defaultDevicePrototype(String name, String description) {
Device device = new Device();
device.setName(name + RandomStringUtils.randomAlphanumeric(7));
device.setType("DEFAULT");
device.setAdditionalInfo(JacksonUtil.newObjectNode().put("description", description));
return device;
}
public static Device defaultDevicePrototype(String name, String description, String label) {
Device device = new Device();
device.setName(name + RandomStringUtils.randomAlphanumeric(7));
device.setType("DEFAULT");
device.setAdditionalInfo(JacksonUtil.newObjectNode().put("description", description));
device.setLabel(label);
return device;
}
public static Device defaultDevicePrototype(String name, boolean gateway) {
Device device = new Device();
device.setName(name + RandomStringUtils.randomAlphanumeric(7));
device.setType("DEFAULT");
device.setAdditionalInfo(JacksonUtil.newObjectNode().put("gateway", gateway));
return device;
}
public static Device defaultDevicePrototype(String name, boolean gateway, boolean overwriteActivityTime) {
Device device = new Device();
device.setName(name + RandomStringUtils.randomAlphanumeric(7));
device.setType("DEFAULT");
device.setAdditionalInfo(JacksonUtil.newObjectNode()
.put("gateway", gateway)
.put("overwriteActivityTime", overwriteActivityTime));
return device;
}
public static Asset defaultAssetPrototype(String name, CustomerId id) {
Asset asset = new Asset();
asset.setName(name + RandomStringUtils.randomAlphanumeric(7));
@ -206,4 +242,10 @@ public class EntityPrototypes {
entityView.setAdditionalInfo(JacksonUtil.newObjectNode().put("entityType", entityType));
return entityView;
}
public static Dashboard defaultDashboardPrototype(String title) {
Dashboard dashboard = new Dashboard();
dashboard.setTitle(title + RandomStringUtils.randomAlphanumeric(7));
return dashboard;
}
}

10
msa/black-box-tests/src/test/resources/all.xml

@ -67,4 +67,14 @@
<package name="org.thingsboard.server.msa.ui.tests.assetProfileSmoke"/>
</packages>
</test>
<test name="Smoke devices tests">
<groups>
<run>
<exclude name="broken"/>
</run>
</groups>
<packages>
<package name="org.thingsboard.server.msa.ui.tests.devicessmoke"/>
</packages>
</test>
</suite>

35
msa/black-box-tests/src/test/resources/smokeDevices.xml

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
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.
-->
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="Devices Smoke Tests">
<listeners>
<listener class-name="org.thingsboard.server.msa.ui.listeners.RetryTestListener"/>
</listeners>
<test name="Devices ui smoke tests">
<groups>
<run>
<exclude name="broken"/>
</run>
</groups>
<packages>
<package name="org.thingsboard.server.msa.ui.tests.devicessmoke"/>
</packages>
</test>
</suite>

10
msa/black-box-tests/src/test/resources/uiTests.xml

@ -62,4 +62,14 @@
<package name="org.thingsboard.server.msa.ui.tests.assetProfileSmoke"/>
</packages>
</test>
<test name="Smoke devices tests">
<groups>
<run>
<exclude name="broken"/>
</run>
</groups>
<packages>
<package name="org.thingsboard.server.msa.ui.tests.devicessmoke"/>
</packages>
</test>
</suite>

2
msa/js-executor/package.json

@ -1,7 +1,7 @@
{
"name": "thingsboard-js-executor",
"private": true,
"version": "3.5.0",
"version": "3.5.1",
"description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.ts",
"bin": "server.js",

2
msa/js-executor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/monitoring/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>

2
msa/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>msa</artifactId>

2
msa/tb-node/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/tb/docker-cassandra/Dockerfile

@ -47,7 +47,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends wget nmap procps gnupg2 \
&& echo "deb http://apt.postgresql.org/pub/repos/apt/ $(. /etc/os-release && echo -n $VERSION_CODENAME)-pgdg main" | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null \
&& wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O- | apt-key add - \
&& echo 'deb http://downloads.apache.org/cassandra/debian 40x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null \
&& echo "deb https://debian.cassandra.apache.org 40x main" | tee -a /etc/apt/sources.list.d/cassandra.sources.list > /dev/null \
&& wget -q https://downloads.apache.org/cassandra/KEYS -O- | apt-key add - \
&& apt-get update \
&& apt-get install -y --no-install-recommends cassandra cassandra-tools postgresql-${PG_MAJOR} \

4
msa/tb/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.1-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>
@ -38,7 +38,7 @@
<tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
<tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.upgradeVersion>3.4.4</pkg.upgradeVersion>
<pkg.upgradeVersion>3.5.0</pkg.upgradeVersion>
</properties>
<dependencies>

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

Loading…
Cancel
Save