Browse Source

Merge branch 'feature/rule-node-debug-strategies' into feature/ui-rule-node-debug-strategies

pull/11861/head
Max Petrov 2 years ago
committed by GitHub
parent
commit
6139cc54e7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 27
      application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
  2. 2
      application/src/main/data/json/system/widget_types/rpc_remote_shell.json
  3. 21
      application/src/main/data/json/tenant/device_profile/rule_chain_template.json
  4. 30
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  5. 6
      application/src/main/data/upgrade/3.8.1/schema_update.sql
  6. 4
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  7. 17
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  8. 9
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  9. 7
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  10. 6
      application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
  11. 2
      application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
  12. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/BaseRuleChainMetadataConstructor.java
  13. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java
  14. 2
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  15. 3
      application/src/main/java/org/thingsboard/server/service/update/DeprecationService.java
  16. 9
      application/src/main/resources/thingsboard.yml
  17. 251
      application/src/test/java/org/thingsboard/server/actors/rule/DefaultTbContextTest.java
  18. 6
      application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
  19. 3
      application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java
  20. 11
      application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
  21. 3
      application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
  22. 5
      application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java
  23. 9
      application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java
  24. 9
      application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java
  25. 33
      common/data/src/main/java/org/thingsboard/server/common/data/HasDebugMode.java
  26. 2
      common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java
  27. 70
      common/data/src/main/java/org/thingsboard/server/common/data/rule/DebugStrategy.java
  28. 22
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java
  29. 9
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  30. 2
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java
  31. 11
      common/edge-api/src/main/proto/edge.proto
  32. 43
      common/util/src/main/java/org/thingsboard/common/util/DebugModeUtil.java
  33. 3
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  34. 20
      dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java
  35. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java
  36. 26
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  37. 4
      dao/src/main/resources/sql/schema-entities.sql
  38. 7
      dao/src/test/java/org/thingsboard/server/dao/service/timeseries/sql/LatestTimeseriesPerformanceTest.java
  39. 54
      monitoring/src/main/resources/root_rule_chain.json
  40. 9
      msa/black-box-tests/src/test/resources/MqttRuleNodeTestMetadata.json
  41. 9
      msa/black-box-tests/src/test/resources/RpcResponseRuleChainMetadata.json
  42. 2
      msa/js-executor/docker/Dockerfile
  43. 48
      msa/js-executor/package.json
  44. 4
      msa/js-executor/pom.xml
  45. 2
      msa/js-executor/queue/awsSqsTemplate.ts
  46. 2
      msa/js-executor/queue/serviceBusTemplate.ts
  47. 4310
      msa/js-executor/yarn.lock
  48. 4
      msa/vc-executor/src/main/resources/tb-vc-executor.yml
  49. 2
      msa/web-ui/docker/Dockerfile
  50. 39
      msa/web-ui/package.json
  51. 4
      msa/web-ui/pom.xml
  52. 1982
      msa/web-ui/yarn.lock
  53. 37
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
  54. 32
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java
  55. 4
      transport/coap/src/main/resources/tb-coap-transport.yml
  56. 4
      transport/http/src/main/resources/tb-http-transport.yml
  57. 4
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  58. 4
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  59. 4
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  60. 2
      ui-ngx/pom.xml
  61. 18
      ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts
  62. 2
      ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html
  63. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/select-dashboard-breakpoint.component.scss
  64. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.scss
  65. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts
  66. 2
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html
  67. 3
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
  68. 8
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts
  69. 6
      ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts
  70. 2
      ui-ngx/src/app/modules/home/pages/customer/customer.component.html
  71. 3
      ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts
  72. 1
      ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.html
  73. 2
      ui-ngx/src/app/shared/components/country-autocomplete.component.html
  74. 5
      ui-ngx/src/app/shared/components/country-autocomplete.component.ts
  75. 4
      ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts
  76. 9
      ui-ngx/src/app/shared/import-export/import-export.service.ts
  77. 65
      ui-ngx/src/app/shared/models/ace/service-completion.models.ts
  78. 3
      ui-ngx/src/app/shared/models/entity.models.ts
  79. 4
      ui-ngx/src/styles.scss

27
application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json

@ -20,7 +20,8 @@
}, },
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node", "name": "Device Profile Node",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"persistAlarmRulesState": false, "persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false "fetchAlarmRulesStateOnStart": false
@ -34,7 +35,8 @@
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries", "name": "Save Timeseries",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"defaultTTL": 0 "defaultTTL": 0
}, },
@ -47,7 +49,8 @@
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes", "name": "Save Client Attributes",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configurationVersion": 2, "configurationVersion": 2,
"configuration": { "configuration": {
"scope": "CLIENT_SCOPE", "scope": "CLIENT_SCOPE",
@ -64,7 +67,8 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch", "name": "Message Type Switch",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"version": 0 "version": 0
}, },
@ -77,7 +81,8 @@
}, },
"type": "org.thingsboard.rule.engine.action.TbLogNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device", "name": "Log RPC from Device",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"scriptLang": "TBEL", "scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
@ -92,7 +97,8 @@
}, },
"type": "org.thingsboard.rule.engine.action.TbLogNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other", "name": "Log Other",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"scriptLang": "TBEL", "scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
@ -107,7 +113,8 @@
}, },
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request", "name": "RPC Call Request",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"timeoutInSeconds": 60 "timeoutInSeconds": 60
}, },
@ -120,7 +127,8 @@
}, },
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode", "type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud", "name": "Push to cloud",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"scope": "SERVER_SCOPE" "scope": "SERVER_SCOPE"
}, },
@ -133,7 +141,8 @@
}, },
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode", "type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud", "name": "Push to cloud",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"scope": "SERVER_SCOPE" "scope": "SERVER_SCOPE"
}, },

2
application/src/main/data/json/system/widget_types/rpc_remote_shell.json

File diff suppressed because one or more lines are too long

21
application/src/main/data/json/tenant/device_profile/rule_chain_template.json

@ -19,7 +19,8 @@
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries", "name": "Save Timeseries",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"defaultTTL": 0 "defaultTTL": 0
} }
@ -31,7 +32,8 @@
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes", "name": "Save Client Attributes",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configurationVersion": 2, "configurationVersion": 2,
"configuration": { "configuration": {
"scope": "CLIENT_SCOPE", "scope": "CLIENT_SCOPE",
@ -47,7 +49,8 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch", "name": "Message Type Switch",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"version": 0 "version": 0
} }
@ -59,7 +62,8 @@
}, },
"type": "org.thingsboard.rule.engine.action.TbLogNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device", "name": "Log RPC from Device",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"scriptLang": "TBEL", "scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
@ -73,7 +77,8 @@
}, },
"type": "org.thingsboard.rule.engine.action.TbLogNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other", "name": "Log Other",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"scriptLang": "TBEL", "scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
@ -87,7 +92,8 @@
}, },
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request", "name": "RPC Call Request",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"timeoutInSeconds": 60 "timeoutInSeconds": 60
} }
@ -100,7 +106,8 @@
}, },
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node", "name": "Device Profile Node",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"configuration": { "configuration": {
"persistAlarmRulesState": false, "persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false "fetchAlarmRulesStateOnStart": false

30
application/src/main/data/json/tenant/rule_chains/root_rule_chain.json

@ -18,7 +18,9 @@
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries", "name": "Save Timeseries",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": { "configuration": {
"defaultTTL": 0 "defaultTTL": 0
} }
@ -30,7 +32,9 @@
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes", "name": "Save Client Attributes",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configurationVersion": 2, "configurationVersion": 2,
"configuration": { "configuration": {
"scope": "CLIENT_SCOPE", "scope": "CLIENT_SCOPE",
@ -46,7 +50,9 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch", "name": "Message Type Switch",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": { "configuration": {
"version": 0 "version": 0
} }
@ -58,7 +64,9 @@
}, },
"type": "org.thingsboard.rule.engine.action.TbLogNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device", "name": "Log RPC from Device",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": { "configuration": {
"scriptLang": "TBEL", "scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
@ -72,7 +80,9 @@
}, },
"type": "org.thingsboard.rule.engine.action.TbLogNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other", "name": "Log Other",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": { "configuration": {
"scriptLang": "TBEL", "scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
@ -86,7 +96,9 @@
}, },
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request", "name": "RPC Call Request",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": { "configuration": {
"timeoutInSeconds": 60 "timeoutInSeconds": 60
} }
@ -99,7 +111,9 @@
}, },
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node", "name": "Device Profile Node",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": { "configuration": {
"persistAlarmRulesState": false, "persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false "fetchAlarmRulesStateOnStart": false
@ -140,4 +154,4 @@
], ],
"ruleChainConnections": null "ruleChainConnections": null
} }
} }

6
application/src/main/data/upgrade/3.8.1/schema_update.sql

@ -28,9 +28,9 @@ UPDATE tb_user SET additional_info = (additional_info::jsonb - 'lastLoginTs' - '
-- UPDATE RULE NODE DEBUG MODE TO DEBUG STRATEGY START -- UPDATE RULE NODE DEBUG MODE TO DEBUG STRATEGY START
ALTER TABLE rule_node ALTER TABLE rule_node
ADD COLUMN IF NOT EXISTS debug_strategy varchar(32) DEFAULT 'DISABLED'; ADD COLUMN IF NOT EXISTS debug_failures boolean DEFAULT false;
ALTER TABLE rule_node ALTER TABLE rule_node
ADD COLUMN IF NOT EXISTS last_update_ts bigint NOT NULL DEFAULT extract(epoch from now()) * 1000; ADD COLUMN IF NOT EXISTS debug_all_until bigint NOT NULL DEFAULT 0;
DO DO
$$ $$
BEGIN BEGIN
@ -38,7 +38,7 @@ $$
FROM information_schema.columns FROM information_schema.columns
WHERE table_name = 'rule_node' AND column_name = 'debug_mode') THEN WHERE table_name = 'rule_node' AND column_name = 'debug_mode') THEN
UPDATE rule_node UPDATE rule_node
SET debug_strategy = CASE WHEN debug_mode = true THEN 'ALL_EVENTS' ELSE 'DISABLED' END; SET debug_all_until = CASE WHEN debug_mode = true THEN extract(epoch from now() + 3600) * 1000 ELSE 0 END;
ALTER TABLE rule_node ALTER TABLE rule_node
DROP COLUMN debug_mode; DROP COLUMN debug_mode;
END IF; END IF;

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

@ -577,9 +577,9 @@ public class ActorSystemContext {
@Getter @Getter
private boolean externalNodeForceAck; private boolean externalNodeForceAck;
@Value("${actors.rule.node.max_debug_mode_duration:60}") @Value("${debug_mode.max_duration:60}")
@Getter @Getter
private int maxRuleNodeDebugModeDurationMinutes; private int maxDebugModeDurationMinutes;
@Value("${state.rule.node.deviceState.rateLimit:1:1,30:60,60:3600}") @Value("${state.rule.node.deviceState.rateLimit:1:1,30:60,60:3600}")
@Getter @Getter

17
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -63,8 +63,8 @@ import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.DebugStrategy;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.common.util.DebugModeUtil;
import org.thingsboard.server.common.data.rule.RuleNodeState; import org.thingsboard.server.common.data.rule.RuleNodeState;
import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
@ -1002,22 +1002,11 @@ public class DefaultTbContext implements TbContext {
private void persistDebugOutput(TbMsg msg, Set<String> relationTypes, Throwable error, String failureMessage) { private void persistDebugOutput(TbMsg msg, Set<String> relationTypes, Throwable error, String failureMessage) {
RuleNode ruleNode = nodeCtx.getSelf(); RuleNode ruleNode = nodeCtx.getSelf();
DebugStrategy debugStrategy = ruleNode.getDebugStrategy(); if (DebugModeUtil.isDebugAllAvailable(ruleNode)) {
if (debugStrategy.shouldPersistDebugOutputForAllEvents(ruleNode.getLastUpdateTs(), msg.getTs(), getMaxRuleNodeDebugDurationMinutes())) {
relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(getTenantId(), ruleNode.getId(), msg, relationType, error, failureMessage)); relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(getTenantId(), ruleNode.getId(), msg, relationType, error, failureMessage));
} else if (debugStrategy.shouldPersistDebugForFailureEvent(relationTypes)) { } else if (DebugModeUtil.isDebugFailuresAvailable(ruleNode, relationTypes)) {
mainCtx.persistDebugOutput(getTenantId(), ruleNode.getId(), msg, TbNodeConnectionType.FAILURE, error, failureMessage); mainCtx.persistDebugOutput(getTenantId(), ruleNode.getId(), msg, TbNodeConnectionType.FAILURE, error, failureMessage);
} }
} }
private int getMaxRuleNodeDebugDurationMinutes() {
if (nodeCtx.getSelf().getDebugStrategy().isHasDuration()) {
var configuration = mainCtx.getTenantProfileCache()
.get(getTenantId()).getProfileData().getConfiguration();
int systemMaxRuleNodeDebugModeDurationMinutes = mainCtx.getMaxRuleNodeDebugModeDurationMinutes();
return configuration.getMaxRuleNodeDebugModeDurationMinutes(systemMaxRuleNodeDebugModeDurationMinutes);
}
return 0;
}
} }

9
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.common.util.DebugModeUtil;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.plugin.RuleNodeUpdatedMsg; import org.thingsboard.server.common.msg.plugin.RuleNodeUpdatedMsg;
@ -255,13 +256,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
var originatorNodeId = envelope.getTargetRuleNodeId(); var originatorNodeId = envelope.getTargetRuleNodeId();
RuleNodeCtx ruleNodeCtx = nodeActors.get(originatorNodeId); RuleNodeCtx ruleNodeCtx = nodeActors.get(originatorNodeId);
if (ruleNodeCtx != null) { if (ruleNodeCtx != null) {
var ruleNode = ruleNodeCtx.getSelf(); if (DebugModeUtil.isDebugAvailable(ruleNodeCtx.getSelf(), envelope.getRelationType())) {
var debugStrategy = ruleNode.getDebugStrategy();
int maxRuleNodeDebugModeDurationMinutes = getTenantProfileConfiguration()
.getMaxRuleNodeDebugModeDurationMinutes(systemContext.getMaxRuleNodeDebugModeDurationMinutes());
boolean shouldPersistDebugOutput = debugStrategy.shouldPersistDebugOutputForAllEvents(ruleNode.getLastUpdateTs(), tbMsg.getTs(), maxRuleNodeDebugModeDurationMinutes) ||
debugStrategy.shouldPersistDebugForFailureEvent(envelope.getRelationType());
if (shouldPersistDebugOutput) {
systemContext.persistDebugOutput(tenantId, originatorNodeId, tbMsg, envelope.getRelationType()); systemContext.persistDebugOutput(tenantId, originatorNodeId, tbMsg, envelope.getRelationType());
} }
} }

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

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.common.util.DebugModeUtil;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.RuleNodeException; import org.thingsboard.server.common.msg.queue.RuleNodeException;
@ -130,7 +131,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
if (maxRuleNodeExecutionsPerMessage == 0 || ruleNodeCount < maxRuleNodeExecutionsPerMessage) { if (maxRuleNodeExecutionsPerMessage == 0 || ruleNodeCount < maxRuleNodeExecutionsPerMessage) {
apiUsageClient.report(tenantId, tbMsg.getCustomerId(), ApiUsageRecordKey.RE_EXEC_COUNT); apiUsageClient.report(tenantId, tbMsg.getCustomerId(), ApiUsageRecordKey.RE_EXEC_COUNT);
persistDebugInputIfAllowed(msg.getMsg(), "Self", tenantProfileConfiguration persistDebugInputIfAllowed(msg.getMsg(), "Self", tenantProfileConfiguration
.getMaxRuleNodeDebugModeDurationMinutes(systemContext.getMaxRuleNodeDebugModeDurationMinutes())); .getMaxDebugModeDurationMinutes(systemContext.getMaxDebugModeDurationMinutes()));
try { try {
tbNode.onMsg(defaultCtx, msg.getMsg()); tbNode.onMsg(defaultCtx, msg.getMsg());
} catch (Exception e) { } catch (Exception e) {
@ -154,7 +155,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
if (maxRuleNodeExecutionsPerMessage == 0 || ruleNodeCount < maxRuleNodeExecutionsPerMessage) { if (maxRuleNodeExecutionsPerMessage == 0 || ruleNodeCount < maxRuleNodeExecutionsPerMessage) {
apiUsageClient.report(tenantId, tbMsg.getCustomerId(), ApiUsageRecordKey.RE_EXEC_COUNT); apiUsageClient.report(tenantId, tbMsg.getCustomerId(), ApiUsageRecordKey.RE_EXEC_COUNT);
persistDebugInputIfAllowed(msg.getMsg(), msg.getFromRelationType(), tenantProfileConfiguration persistDebugInputIfAllowed(msg.getMsg(), msg.getFromRelationType(), tenantProfileConfiguration
.getMaxRuleNodeDebugModeDurationMinutes(systemContext.getMaxRuleNodeDebugModeDurationMinutes())); .getMaxDebugModeDurationMinutes(systemContext.getMaxDebugModeDurationMinutes()));
try { try {
tbNode.onMsg(msg.getCtx(), msg.getMsg()); tbNode.onMsg(msg.getCtx(), msg.getMsg());
} catch (Exception e) { } catch (Exception e) {
@ -218,7 +219,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
} }
private void persistDebugInputIfAllowed(TbMsg msg, String fromNodeConnectionType, int debugModeDurationMinutes) { private void persistDebugInputIfAllowed(TbMsg msg, String fromNodeConnectionType, int debugModeDurationMinutes) {
if (ruleNode.getDebugStrategy().shouldPersistDebugInput(ruleNode.getLastUpdateTs(), msg.getTs(), debugModeDurationMinutes)) { if (DebugModeUtil.isDebugAllAvailable(ruleNode)) {
systemContext.persistDebugInput(tenantId, entityId, msg, fromNodeConnectionType); systemContext.persistDebugInput(tenantId, entityId, msg, fromNodeConnectionType);
} }
} }

6
application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java

@ -71,8 +71,8 @@ public class SystemInfoController extends BaseController {
@Value("${ui.dashboard.max_datapoints_limit}") @Value("${ui.dashboard.max_datapoints_limit}")
private long maxDatapointsLimit; private long maxDatapointsLimit;
@Value("${actors.rule.node.max_debug_mode_duration:60}") @Value("${debug_mode.max_duration:60}")
private int maxRuleNodeDebugModeDurationMinutes; private int maxDebugModeDurationMinutes;
@Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled:true}") @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled:true}")
@Getter @Getter
@ -153,7 +153,7 @@ public class SystemInfoController extends BaseController {
if (!currentUser.isSystemAdmin()) { if (!currentUser.isSystemAdmin()) {
DefaultTenantProfileConfiguration tenantProfileConfiguration = tenantProfileCache.get(tenantId).getDefaultProfileConfiguration(); DefaultTenantProfileConfiguration tenantProfileConfiguration = tenantProfileCache.get(tenantId).getDefaultProfileConfiguration();
systemParams.setMaxResourceSize(tenantProfileConfiguration.getMaxResourceSize()); systemParams.setMaxResourceSize(tenantProfileConfiguration.getMaxResourceSize());
systemParams.setMaxRuleNodeDebugDurationMinutes(tenantProfileConfiguration.getMaxRuleNodeDebugModeDurationMinutes(maxRuleNodeDebugModeDurationMinutes)); systemParams.setMaxDebugModeDurationMinutes(tenantProfileConfiguration.getMaxDebugModeDurationMinutes(maxDebugModeDurationMinutes));
if (ruleChainDebugPerTenantLimitsEnabled) { if (ruleChainDebugPerTenantLimitsEnabled) {
systemParams.setRuleChainDebugPerTenantLimitsConfiguration(ruleChainDebugPerTenantLimitsConfiguration); systemParams.setRuleChainDebugPerTenantLimitsConfiguration(ruleChainDebugPerTenantLimitsConfiguration);
} }

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

@ -151,7 +151,7 @@ public class TenantProfileController extends BaseController {
" \"maxJSExecutions\": 5000000,\n" + " \"maxJSExecutions\": 5000000,\n" +
" \"maxDPStorageDays\": 0,\n" + " \"maxDPStorageDays\": 0,\n" +
" \"maxRuleNodeExecutionsPerMessage\": 50,\n" + " \"maxRuleNodeExecutionsPerMessage\": 50,\n" +
" \"maxRuleNodeDebugDurationMinutes\": 15,\n" + " \"maxDebugModeDurationMinutes\": 15,\n" +
" \"maxEmails\": 0,\n" + " \"maxEmails\": 0,\n" +
" \"maxSms\": 0,\n" + " \"maxSms\": 0,\n" +
" \"maxCreatedAlarms\": 1000,\n" + " \"maxCreatedAlarms\": 1000,\n" +

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/BaseRuleChainMetadataConstructor.java

@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.gen.edge.v1.DebugStrategy;
import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto; import org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto;
import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto; import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto;
@ -89,8 +88,8 @@ public abstract class BaseRuleChainMetadataConstructor implements RuleChainMetad
.setIdLSB(node.getId().getId().getLeastSignificantBits()) .setIdLSB(node.getId().getId().getLeastSignificantBits())
.setType(node.getType()) .setType(node.getType())
.setName(node.getName()) .setName(node.getName())
.setLastUpdateTs(node.getLastUpdateTs()) .setDebugFailures(node.isDebugFailures())
.setDebugStrategy(DebugStrategy.forNumber(node.getDebugStrategy().getProtoNumber())) .setDebugAllUntil(node.getDebugAllUntil())
.setConfiguration(JacksonUtil.toString(node.getConfiguration())) .setConfiguration(JacksonUtil.toString(node.getConfiguration()))
.setAdditionalInfo(JacksonUtil.toString(node.getAdditionalInfo())) .setAdditionalInfo(JacksonUtil.toString(node.getAdditionalInfo()))
.setSingletonMode(node.isSingletonMode()) .setSingletonMode(node.isSingletonMode())

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java

@ -88,7 +88,7 @@ public class TenantMsgConstructorV1 implements TenantMsgConstructor {
configuration.setMaxTransportDataPoints(0); configuration.setMaxTransportDataPoints(0);
configuration.setRuleEngineExceptionsTtlDays(0); configuration.setRuleEngineExceptionsTtlDays(0);
configuration.setMaxRuleNodeExecutionsPerMessage(0); configuration.setMaxRuleNodeExecutionsPerMessage(0);
configuration.setMaxRuleNodeDebugDurationMinutes(0); configuration.setMaxDebugModeDurationMinutes(0);
tenantProfileData.setConfiguration(configuration); tenantProfileData.setConfiguration(configuration);
tenantProfile.setProfileData(tenantProfileData); tenantProfile.setProfileData(tenantProfileData);

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

@ -202,7 +202,7 @@ public class InstallScripts {
ruleChain = ruleChainService.saveRuleChain(ruleChain, false); ruleChain = ruleChainService.saveRuleChain(ruleChain, false);
ruleChainMetaData.setRuleChainId(ruleChain.getId()); ruleChainMetaData.setRuleChainId(ruleChain.getId());
ruleChainService.saveRuleChainMetaData(TenantId.SYS_TENANT_ID, ruleChainMetaData, Function.identity(), false); ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData, Function.identity(), false);
return ruleChain; return ruleChain;
} }

3
application/src/main/java/org/thingsboard/server/service/update/DeprecationService.java

@ -55,7 +55,8 @@ public class DeprecationService {
} }
} }
log.warn("WARNING: {} queue type is deprecated and will be removed in ThingsBoard 4.0. Please migrate to Apache Kafka", queueTypeName); log.warn("WARNING: Starting with ThingsBoard 4.0, {} will no longer be supported as a message queue for microservices. " +
"Please migrate to Apache Kafka. This change will not impact any rule nodes", queueTypeName);
notificationCenter.sendGeneralWebNotification(TenantId.SYS_TENANT_ID, new SystemAdministratorsFilter(), notificationCenter.sendGeneralWebNotification(TenantId.SYS_TENANT_ID, new SystemAdministratorsFilter(),
DefaultNotifications.queueTypeDeprecation.toTemplate(), new GeneralNotificationInfo(Map.of( DefaultNotifications.queueTypeDeprecation.toTemplate(), new GeneralNotificationInfo(Map.of(
"queueType", queueTypeName "queueType", queueTypeName

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

@ -495,6 +495,11 @@ actors:
# Actors statistic persistence frequency in milliseconds # Actors statistic persistence frequency in milliseconds
persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}" persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}"
debug_mode:
# The maximum allowed duration (in minutes) for the debug mode to be used.
# If a specific value is set in the tenant profile, the minimum between value from profile and this setting will be used.
max_duration: "${DEBUG_MODE_MAX_DURATION_MINUTES:60}"
# Cache settings parameters # Cache settings parameters
cache: cache:
# caffeine or redis(7.2 - latest compatible version) # caffeine or redis(7.2 - latest compatible version)
@ -1453,7 +1458,9 @@ swagger:
# Queue configuration parameters # Queue configuration parameters
queue: queue:
type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) # in-memory or kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:in-memory}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka). prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
in_memory: in_memory:
stats: stats:

251
application/src/test/java/org/thingsboard/server/actors/rule/DefaultTbContextTest.java

@ -34,24 +34,19 @@ import org.thingsboard.server.actors.ruleChain.RuleNodeCtx;
import org.thingsboard.server.actors.ruleChain.RuleNodeToRuleChainTellNextMsg; import org.thingsboard.server.actors.ruleChain.RuleNodeToRuleChainTellNextMsg;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.rule.DebugStrategy;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.TbMsgProcessingStackItem; import org.thingsboard.server.common.msg.TbMsgProcessingStackItem;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbMsgCallback; import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.queue.common.SimpleTbQueueCallback; import org.thingsboard.server.queue.common.SimpleTbQueueCallback;
@ -59,13 +54,13 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.isNull;
@ -75,7 +70,6 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then; import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@SuppressWarnings("ResultOfMethodCallIgnored") @SuppressWarnings("ResultOfMethodCallIgnored")
@ -104,13 +98,14 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenDebugStrategyOnlyFailureEvents_whenTellSuccess_thenVerifyDebugOutputNotPersisted() { public void givenDebugFailuresEvents_whenTellSuccess_thenVerifyDebugOutputNotPersisted() {
// GIVEN // GIVEN
var callbackMock = mock(TbMsgCallback.class); var callbackMock = mock(TbMsgCallback.class);
var msg = getTbMsgWithCallback(callbackMock); var msg = getTbMsgWithCallback(callbackMock);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ONLY_FAILURE_EVENTS); ruleNode.setDebugFailures(true);
ruleNode.setDebugAllUntil(0);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
@ -125,13 +120,14 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenDebugStrategyOnlyFailureEventsAndSuccessConnection_whenTellNext_thenVerifyDebugOutputNotPersisted() { public void givenDebugFailuresEventsAndSuccessConnection_whenTellNext_thenVerifyDebugOutputNotPersisted() {
// GIVEN // GIVEN
var callbackMock = mock(TbMsgCallback.class); var callbackMock = mock(TbMsgCallback.class);
var msg = getTbMsgWithCallback(callbackMock); var msg = getTbMsgWithCallback(callbackMock);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ONLY_FAILURE_EVENTS); ruleNode.setDebugFailures(true);
ruleNode.setDebugAllUntil(0);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
@ -147,13 +143,14 @@ class DefaultTbContextTest {
@MethodSource @MethodSource
@ParameterizedTest @ParameterizedTest
void givenDebugStrategyOnlyFailureEventsAndConnections_whenTellNext_thenVerifyDebugOutputPersisted(Set<String> connections) { void givenDebugFailuresEventsAndConnections_whenTellNext_thenVerifyDebugOutputPersisted(Set<String> connections) {
// GIVEN // GIVEN
var callbackMock = mock(TbMsgCallback.class); var callbackMock = mock(TbMsgCallback.class);
var msg = getTbMsgWithCallback(callbackMock); var msg = getTbMsgWithCallback(callbackMock);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ONLY_FAILURE_EVENTS); ruleNode.setDebugFailures(true);
ruleNode.setDebugAllUntil(0);
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
@ -169,7 +166,7 @@ class DefaultTbContextTest {
checkTellNextCommonLogic(callbackMock, connections, msg); checkTellNextCommonLogic(callbackMock, connections, msg);
} }
private static Stream<Set<String>> givenDebugStrategyOnlyFailureEventsAndConnections_whenTellNext_thenVerifyDebugOutputPersisted() { private static Stream<Set<String>> givenDebugFailuresEventsAndConnections_whenTellNext_thenVerifyDebugOutputPersisted() {
return Stream.of( return Stream.of(
Collections.singleton(TbNodeConnectionType.FAILURE), Collections.singleton(TbNodeConnectionType.FAILURE),
Set.of(TbNodeConnectionType.FAILURE, TbNodeConnectionType.SUCCESS) Set.of(TbNodeConnectionType.FAILURE, TbNodeConnectionType.SUCCESS)
@ -178,13 +175,14 @@ class DefaultTbContextTest {
@MethodSource @MethodSource
@ParameterizedTest @ParameterizedTest
void givenDebugStrategyDisabledAndConnections_whenTellNext_thenVerifyDebugOutputNotPersisted(Set<String> connections) { void givenDebugDisabledAndConnections_whenTellNext_thenVerifyDebugOutputNotPersisted(Set<String> connections) {
// GIVEN // GIVEN
var callbackMock = mock(TbMsgCallback.class); var callbackMock = mock(TbMsgCallback.class);
var msg = getTbMsgWithCallback(callbackMock); var msg = getTbMsgWithCallback(callbackMock);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.DISABLED); ruleNode.setDebugFailures(false);
ruleNode.setDebugAllUntil(0);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
@ -198,7 +196,7 @@ class DefaultTbContextTest {
checkTellNextCommonLogic(callbackMock, connections, msg); checkTellNextCommonLogic(callbackMock, connections, msg);
} }
private static Stream<Set<String>> givenDebugStrategyDisabledAndConnections_whenTellNext_thenVerifyDebugOutputNotPersisted() { private static Stream<Set<String>> givenDebugDisabledAndConnections_whenTellNext_thenVerifyDebugOutputNotPersisted() {
return Stream.of( return Stream.of(
Collections.singleton(TbNodeConnectionType.FAILURE), Collections.singleton(TbNodeConnectionType.FAILURE),
Collections.singleton(TbNodeConnectionType.SUCCESS), Collections.singleton(TbNodeConnectionType.SUCCESS),
@ -208,18 +206,17 @@ class DefaultTbContextTest {
@MethodSource @MethodSource
@ParameterizedTest @ParameterizedTest
void givenDebugStrategyAllEventsAndConnection_whenTellNext_thenVerifyDebugOutputPersisted(String connection) { void givenDebugAllEventsAndConnection_whenTellNext_thenVerifyDebugOutputPersisted(String connection) {
// GIVEN // GIVEN
var callbackMock = mock(TbMsgCallback.class); var callbackMock = mock(TbMsgCallback.class);
var msg = getTbMsgWithCallback(callbackMock); var msg = getTbMsgWithCallback(callbackMock);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setLastUpdateTs(System.currentTimeMillis()); ruleNode.setDebugFailures(false);
ruleNode.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode.setDebugAllUntil(getUntilTime());
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
mockGetMaxRuleNodeDebugModeDurationMinutes();
// WHEN // WHEN
defaultTbContext.tellNext(msg, connection); defaultTbContext.tellNext(msg, connection);
@ -227,30 +224,27 @@ class DefaultTbContextTest {
// THEN // THEN
then(nodeCtxMock).should().getChainActor(); then(nodeCtxMock).should().getChainActor();
then(nodeCtxMock).shouldHaveNoMoreInteractions(); then(nodeCtxMock).shouldHaveNoMoreInteractions();
then(mainCtxMock).should().getTenantProfileCache();
then(mainCtxMock).should().getMaxRuleNodeDebugModeDurationMinutes();
then(mainCtxMock).should().persistDebugOutput(TENANT_ID, RULE_NODE_ID, msg, connection, null, null); then(mainCtxMock).should().persistDebugOutput(TENANT_ID, RULE_NODE_ID, msg, connection, null, null);
then(mainCtxMock).shouldHaveNoMoreInteractions(); then(mainCtxMock).shouldHaveNoMoreInteractions();
checkTellNextCommonLogic(callbackMock, connection, msg); checkTellNextCommonLogic(callbackMock, connection, msg);
} }
private static Stream<String> givenDebugStrategyAllEventsAndConnection_whenTellNext_thenVerifyDebugOutputPersisted() { private static Stream<String> givenDebugAllEventsAndConnection_whenTellNext_thenVerifyDebugOutputPersisted() {
return failureAndSuccessConnection(); return failureAndSuccessConnection();
} }
@Test @Test
public void givenDebugStrategyAllEventsAndFailureAndSuccessConnection_whenTellNext_thenVerifyDebugOutputPersistedForAllEvents() { public void givenDebugAllEventsAndFailureAndSuccessConnection_whenTellNext_thenVerifyDebugOutputPersistedForAllEvents() {
// GIVEN // GIVEN
var callbackMock = mock(TbMsgCallback.class); var callbackMock = mock(TbMsgCallback.class);
var msg = getTbMsgWithCallback(callbackMock); var msg = getTbMsgWithCallback(callbackMock);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setLastUpdateTs(System.currentTimeMillis()); ruleNode.setDebugFailures(false);
ruleNode.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode.setDebugAllUntil(getUntilTime());
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
mockGetMaxRuleNodeDebugModeDurationMinutes();
// WHEN // WHEN
Set<String> connections = failureAndSuccessConnection().collect(Collectors.toSet()); Set<String> connections = failureAndSuccessConnection().collect(Collectors.toSet());
@ -259,8 +253,6 @@ class DefaultTbContextTest {
// THEN // THEN
then(nodeCtxMock).should().getChainActor(); then(nodeCtxMock).should().getChainActor();
then(nodeCtxMock).shouldHaveNoMoreInteractions(); then(nodeCtxMock).shouldHaveNoMoreInteractions();
then(mainCtxMock).should().getTenantProfileCache();
then(mainCtxMock).should().getMaxRuleNodeDebugModeDurationMinutes();
var nodeConnectionsCaptor = ArgumentCaptor.forClass(String.class); var nodeConnectionsCaptor = ArgumentCaptor.forClass(String.class);
int wantedNumberOfInvocations = connections.size(); int wantedNumberOfInvocations = connections.size();
then(mainCtxMock).should(times(wantedNumberOfInvocations)).persistDebugOutput(eq(TENANT_ID), eq(RULE_NODE_ID), eq(msg), nodeConnectionsCaptor.capture(), nullable(Throwable.class), nullable(String.class)); then(mainCtxMock).should(times(wantedNumberOfInvocations)).persistDebugOutput(eq(TENANT_ID), eq(RULE_NODE_ID), eq(msg), nodeConnectionsCaptor.capture(), nullable(Throwable.class), nullable(String.class));
@ -272,18 +264,17 @@ class DefaultTbContextTest {
@MethodSource @MethodSource
@ParameterizedTest @ParameterizedTest
void givenDebugStrategyAllThenOnlyFailureEventsAndConnection_whenTellNext_thenVerifyDebugOutputPersisted(String connection) { void givenDebugAllThenOnlyFailureEventsAndConnection_whenTellNext_thenVerifyDebugOutputPersisted(String connection) {
// GIVEN // GIVEN
var callbackMock = mock(TbMsgCallback.class); var callbackMock = mock(TbMsgCallback.class);
var msg = getTbMsgWithCallback(callbackMock); var msg = getTbMsgWithCallback(callbackMock);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setLastUpdateTs(System.currentTimeMillis()); ruleNode.setDebugFailures(false);
ruleNode.setDebugStrategy(DebugStrategy.ALL_THEN_ONLY_FAILURE_EVENTS); ruleNode.setDebugAllUntil(getUntilTime());
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
mockGetMaxRuleNodeDebugModeDurationMinutes();
// WHEN // WHEN
defaultTbContext.tellNext(msg, connection); defaultTbContext.tellNext(msg, connection);
@ -291,30 +282,27 @@ class DefaultTbContextTest {
// THEN // THEN
then(nodeCtxMock).should().getChainActor(); then(nodeCtxMock).should().getChainActor();
then(nodeCtxMock).shouldHaveNoMoreInteractions(); then(nodeCtxMock).shouldHaveNoMoreInteractions();
then(mainCtxMock).should().getTenantProfileCache();
then(mainCtxMock).should().getMaxRuleNodeDebugModeDurationMinutes();
then(mainCtxMock).should().persistDebugOutput(TENANT_ID, RULE_NODE_ID, msg, connection, null, null); then(mainCtxMock).should().persistDebugOutput(TENANT_ID, RULE_NODE_ID, msg, connection, null, null);
then(mainCtxMock).shouldHaveNoMoreInteractions(); then(mainCtxMock).shouldHaveNoMoreInteractions();
checkTellNextCommonLogic(callbackMock, connection, msg); checkTellNextCommonLogic(callbackMock, connection, msg);
} }
private static Stream<String> givenDebugStrategyAllThenOnlyFailureEventsAndConnection_whenTellNext_thenVerifyDebugOutputPersisted() { private static Stream<String> givenDebugAllThenOnlyFailureEventsAndConnection_whenTellNext_thenVerifyDebugOutputPersisted() {
return failureAndSuccessConnection(); return failureAndSuccessConnection();
} }
@Test @Test
public void givenDebugStrategyAllThenOnlyEventsAndFailureAndSuccessConnection_whenTellNext_thenVerifyDebugOutputPersistedForAllEvents() { public void givenDebugAllThenOnlyEventsAndFailureAndSuccessConnection_whenTellNext_thenVerifyDebugOutputPersistedForAllEvents() {
// GIVEN // GIVEN
var callbackMock = mock(TbMsgCallback.class); var callbackMock = mock(TbMsgCallback.class);
var msg = getTbMsgWithCallback(callbackMock); var msg = getTbMsgWithCallback(callbackMock);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setLastUpdateTs(System.currentTimeMillis()); ruleNode.setDebugFailures(true);
ruleNode.setDebugStrategy(DebugStrategy.ALL_THEN_ONLY_FAILURE_EVENTS); ruleNode.setDebugAllUntil(getUntilTime());
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
mockGetMaxRuleNodeDebugModeDurationMinutes();
// WHEN // WHEN
Set<String> connections = failureAndSuccessConnection().collect(Collectors.toSet()); Set<String> connections = failureAndSuccessConnection().collect(Collectors.toSet());
@ -323,8 +311,6 @@ class DefaultTbContextTest {
// THEN // THEN
then(nodeCtxMock).should().getChainActor(); then(nodeCtxMock).should().getChainActor();
then(nodeCtxMock).shouldHaveNoMoreInteractions(); then(nodeCtxMock).shouldHaveNoMoreInteractions();
then(mainCtxMock).should().getTenantProfileCache();
then(mainCtxMock).should().getMaxRuleNodeDebugModeDurationMinutes();
var nodeConnectionsCaptor = ArgumentCaptor.forClass(String.class); var nodeConnectionsCaptor = ArgumentCaptor.forClass(String.class);
int wantedNumberOfInvocations = connections.size(); int wantedNumberOfInvocations = connections.size();
then(mainCtxMock).should(times(wantedNumberOfInvocations)).persistDebugOutput(eq(TENANT_ID), eq(RULE_NODE_ID), eq(msg), nodeConnectionsCaptor.capture(), nullable(Throwable.class), nullable(String.class)); then(mainCtxMock).should(times(wantedNumberOfInvocations)).persistDebugOutput(eq(TENANT_ID), eq(RULE_NODE_ID), eq(msg), nodeConnectionsCaptor.capture(), nullable(Throwable.class), nullable(String.class));
@ -339,12 +325,12 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenDebugStrategyOnlyFailureEventsAndFailureConnection_whenOutput_thenVerifyDebugOutputPersisted() { public void givenDebugFailuresEventsAndFailureConnection_whenOutput_thenVerifyDebugOutputPersisted() {
// GIVEN // GIVEN
var msgMock = mock(TbMsg.class); var msgMock = mock(TbMsg.class);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ONLY_FAILURE_EVENTS); ruleNode.setDebugFailures(true);
given(msgMock.popFormStack()).willReturn(new TbMsgProcessingStackItem(RULE_CHAIN_ID, RULE_NODE_ID)); given(msgMock.popFormStack()).willReturn(new TbMsgProcessingStackItem(RULE_CHAIN_ID, RULE_NODE_ID));
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
@ -361,12 +347,12 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenDebugStrategyOnlyFailureEventsAndSuccessConnection_whenOutput_thenVerifyDebugOutputNotPersisted() { public void givenDebugFailuresEventsAndSuccessConnection_whenOutput_thenVerifyDebugOutputNotPersisted() {
// GIVEN // GIVEN
var msgMock = mock(TbMsg.class); var msgMock = mock(TbMsg.class);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ONLY_FAILURE_EVENTS); ruleNode.setDebugFailures(true);
given(msgMock.popFormStack()).willReturn(new TbMsgProcessingStackItem(RULE_CHAIN_ID, RULE_NODE_ID)); given(msgMock.popFormStack()).willReturn(new TbMsgProcessingStackItem(RULE_CHAIN_ID, RULE_NODE_ID));
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
@ -382,12 +368,13 @@ class DefaultTbContextTest {
@ParameterizedTest @ParameterizedTest
@ValueSource(strings = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE}) @ValueSource(strings = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE})
void givenDebugStrategyDisabled_whenOutput_thenVerifyDebugOutputNotPersisted(String nodeConnection) { void givenDebugDisabled_whenOutput_thenVerifyDebugOutputNotPersisted(String nodeConnection) {
// GIVEN // GIVEN
var msgMock = mock(TbMsg.class); var msgMock = mock(TbMsg.class);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.DISABLED); ruleNode.setDebugFailures(false);
ruleNode.setDebugAllUntil(0);
given(msgMock.popFormStack()).willReturn(new TbMsgProcessingStackItem(RULE_CHAIN_ID, RULE_NODE_ID)); given(msgMock.popFormStack()).willReturn(new TbMsgProcessingStackItem(RULE_CHAIN_ID, RULE_NODE_ID));
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
@ -403,25 +390,23 @@ class DefaultTbContextTest {
@ParameterizedTest @ParameterizedTest
@ValueSource(strings = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE}) @ValueSource(strings = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE})
void givenDebugStrategyAllEvents_whenOutput_thenVerifyDebugOutputPersisted(String nodeConnection) { void givenDebugAllEvents_whenOutput_thenVerifyDebugOutputPersisted(String nodeConnection) {
// GIVEN // GIVEN
var msgMock = mock(TbMsg.class); var msgMock = mock(TbMsg.class);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode.setDebugFailures(false);
ruleNode.setDebugAllUntil(getUntilTime());
given(msgMock.popFormStack()).willReturn(new TbMsgProcessingStackItem(RULE_CHAIN_ID, RULE_NODE_ID)); given(msgMock.popFormStack()).willReturn(new TbMsgProcessingStackItem(RULE_CHAIN_ID, RULE_NODE_ID));
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
mockGetMaxRuleNodeDebugModeDurationMinutes();
// WHEN // WHEN
defaultTbContext.output(msgMock, nodeConnection); defaultTbContext.output(msgMock, nodeConnection);
// THEN // THEN
checkOutputCommonLogic(msgMock, nodeConnection); checkOutputCommonLogic(msgMock, nodeConnection);
then(mainCtxMock).should().getTenantProfileCache();
then(mainCtxMock).should().getMaxRuleNodeDebugModeDurationMinutes();
then(mainCtxMock).should().persistDebugOutput(TENANT_ID, RULE_NODE_ID, msgMock, nodeConnection, null, null); then(mainCtxMock).should().persistDebugOutput(TENANT_ID, RULE_NODE_ID, msgMock, nodeConnection, null, null);
then(mainCtxMock).shouldHaveNoMoreInteractions(); then(mainCtxMock).shouldHaveNoMoreInteractions();
then(nodeCtxMock).shouldHaveNoMoreInteractions(); then(nodeCtxMock).shouldHaveNoMoreInteractions();
@ -429,25 +414,23 @@ class DefaultTbContextTest {
@ParameterizedTest @ParameterizedTest
@ValueSource(strings = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE}) @ValueSource(strings = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE})
void givenDebugStrategyAllThenOnlyFailureEvents_whenOutput_thenVerifyDebugOutputPersisted(String nodeConnection) { void givenDebugAllThenOnlyFailureEvents_whenOutput_thenVerifyDebugOutputPersisted(String nodeConnection) {
// GIVEN // GIVEN
var msgMock = mock(TbMsg.class); var msgMock = mock(TbMsg.class);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode.setDebugFailures(false);
ruleNode.setDebugAllUntil(getUntilTime());
given(msgMock.popFormStack()).willReturn(new TbMsgProcessingStackItem(RULE_CHAIN_ID, RULE_NODE_ID)); given(msgMock.popFormStack()).willReturn(new TbMsgProcessingStackItem(RULE_CHAIN_ID, RULE_NODE_ID));
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
mockGetMaxRuleNodeDebugModeDurationMinutes();
// WHEN // WHEN
defaultTbContext.output(msgMock, nodeConnection); defaultTbContext.output(msgMock, nodeConnection);
// THEN // THEN
checkOutputCommonLogic(msgMock, nodeConnection); checkOutputCommonLogic(msgMock, nodeConnection);
then(mainCtxMock).should().getTenantProfileCache();
then(mainCtxMock).should().getMaxRuleNodeDebugModeDurationMinutes();
then(mainCtxMock).should().persistDebugOutput(TENANT_ID, RULE_NODE_ID, msgMock, nodeConnection, null, null); then(mainCtxMock).should().persistDebugOutput(TENANT_ID, RULE_NODE_ID, msgMock, nodeConnection, null, null);
then(mainCtxMock).shouldHaveNoMoreInteractions(); then(mainCtxMock).shouldHaveNoMoreInteractions();
then(nodeCtxMock).shouldHaveNoMoreInteractions(); then(nodeCtxMock).shouldHaveNoMoreInteractions();
@ -459,7 +442,8 @@ class DefaultTbContextTest {
var msgMock = mock(TbMsg.class); var msgMock = mock(TbMsg.class);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.DISABLED); ruleNode.setDebugFailures(false);
ruleNode.setDebugAllUntil(0);
given(msgMock.popFormStack()).willReturn(null); given(msgMock.popFormStack()).willReturn(null);
TbMsgCallback callbackMock = mock(TbMsgCallback.class); TbMsgCallback callbackMock = mock(TbMsgCallback.class);
given(msgMock.getCallback()).willReturn(callbackMock); given(msgMock.getCallback()).willReturn(callbackMock);
@ -476,19 +460,18 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenEmptyStackAndDebugStrategyAllEvents_whenOutput_thenVerifyMsgAckAndDebugOutputPersisted() { public void givenEmptyStackAndDebugAllEvents_whenOutput_thenVerifyMsgAckAndDebugOutputPersisted() {
// GIVEN // GIVEN
var msgMock = mock(TbMsg.class); var msgMock = mock(TbMsg.class);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode.setDebugFailures(false);
ruleNode.setLastUpdateTs(System.currentTimeMillis()); ruleNode.setDebugAllUntil(getUntilTime());
given(msgMock.popFormStack()).willReturn(null); given(msgMock.popFormStack()).willReturn(null);
TbMsgCallback callbackMock = mock(TbMsgCallback.class); TbMsgCallback callbackMock = mock(TbMsgCallback.class);
given(msgMock.getCallback()).willReturn(callbackMock); given(msgMock.getCallback()).willReturn(callbackMock);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
mockGetMaxRuleNodeDebugModeDurationMinutes();
// WHEN // WHEN
defaultTbContext.output(msgMock, TbNodeConnectionType.SUCCESS); defaultTbContext.output(msgMock, TbNodeConnectionType.SUCCESS);
@ -502,19 +485,18 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenEmptyStackAndDebugStrategyAllThenOnlyFailureEvents_whenOutput_thenVerifyMsgAckAndDebugOutputPersisted() { public void givenEmptyStackAndDebugAllThenOnlyFailureEvents_whenOutput_thenVerifyMsgAckAndDebugOutputPersisted() {
// GIVEN // GIVEN
var msgMock = mock(TbMsg.class); var msgMock = mock(TbMsg.class);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ALL_THEN_ONLY_FAILURE_EVENTS); ruleNode.setDebugFailures(true);
ruleNode.setLastUpdateTs(System.currentTimeMillis()); ruleNode.setDebugAllUntil(getUntilTime());
given(msgMock.popFormStack()).willReturn(null); given(msgMock.popFormStack()).willReturn(null);
TbMsgCallback callbackMock = mock(TbMsgCallback.class); TbMsgCallback callbackMock = mock(TbMsgCallback.class);
given(msgMock.getCallback()).willReturn(callbackMock); given(msgMock.getCallback()).willReturn(callbackMock);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
mockGetMaxRuleNodeDebugModeDurationMinutes();
// WHEN // WHEN
defaultTbContext.output(msgMock, TbNodeConnectionType.SUCCESS); defaultTbContext.output(msgMock, TbNodeConnectionType.SUCCESS);
@ -528,13 +510,13 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenDebugStrategyOnlyFailureEvents_whenEnqueueForTellFailure_thenVerifyDebugOutputPersisted() { public void givenDebugFailuresEvents_whenEnqueueForTellFailure_thenVerifyDebugOutputPersisted() {
// GIVEN // GIVEN
var msg = getTbMsgWithQueueName(); var msg = getTbMsgWithQueueName();
var tpi = new TopicPartitionInfo(DataConstants.MAIN_QUEUE_TOPIC, TENANT_ID, 0, true); var tpi = new TopicPartitionInfo(DataConstants.MAIN_QUEUE_TOPIC, TENANT_ID, 0, true);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ONLY_FAILURE_EVENTS); ruleNode.setDebugFailures(true);
var tbClusterServiceMock = mock(TbClusterService.class); var tbClusterServiceMock = mock(TbClusterService.class);
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
@ -561,13 +543,14 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenDebugStrategyDisabled_whenEnqueueForTellFailure_thenVerifyDebugOutputNotPersisted() { public void givenDebugDisabled_whenEnqueueForTellFailure_thenVerifyDebugOutputNotPersisted() {
// GIVEN // GIVEN
var msg = getTbMsgWithQueueName(); var msg = getTbMsgWithQueueName();
var tpi = new TopicPartitionInfo(DataConstants.MAIN_QUEUE_TOPIC, TENANT_ID, 0, true); var tpi = new TopicPartitionInfo(DataConstants.MAIN_QUEUE_TOPIC, TENANT_ID, 0, true);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.DISABLED); ruleNode.setDebugFailures(false);
ruleNode.setDebugAllUntil(0);
var tbClusterServiceMock = mock(TbClusterService.class); var tbClusterServiceMock = mock(TbClusterService.class);
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
@ -588,21 +571,20 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenDebugStrategyAllEvents_whenEnqueueForTellFailure_thenVerifyDebugOutputPersisted() { public void givenDebugAllEvents_whenEnqueueForTellFailure_thenVerifyDebugOutputPersisted() {
// GIVEN // GIVEN
var msg = getTbMsgWithQueueName(); var msg = getTbMsgWithQueueName();
var tpi = new TopicPartitionInfo(DataConstants.MAIN_QUEUE_TOPIC, TENANT_ID, 0, true); var tpi = new TopicPartitionInfo(DataConstants.MAIN_QUEUE_TOPIC, TENANT_ID, 0, true);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode.setDebugFailures(false);
ruleNode.setLastUpdateTs(System.currentTimeMillis()); ruleNode.setDebugAllUntil(getUntilTime());
var tbClusterServiceMock = mock(TbClusterService.class); var tbClusterServiceMock = mock(TbClusterService.class);
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(mainCtxMock.resolve(any(ServiceType.class), anyString(), any(TenantId.class), any(EntityId.class))).willReturn(tpi); given(mainCtxMock.resolve(any(ServiceType.class), anyString(), any(TenantId.class), any(EntityId.class))).willReturn(tpi);
given(mainCtxMock.getClusterService()).willReturn(tbClusterServiceMock); given(mainCtxMock.getClusterService()).willReturn(tbClusterServiceMock);
mockGetMaxRuleNodeDebugModeDurationMinutes();
// WHEN // WHEN
defaultTbContext.enqueueForTellFailure(msg, EXCEPTION); defaultTbContext.enqueueForTellFailure(msg, EXCEPTION);
@ -618,8 +600,6 @@ class DefaultTbContextTest {
.ignoringFields("id", "ctx") .ignoringFields("id", "ctx")
.isEqualTo(expectedTbMsg); .isEqualTo(expectedTbMsg);
then(mainCtxMock).should().getClusterService(); then(mainCtxMock).should().getClusterService();
then(mainCtxMock).should().getTenantProfileCache();
then(mainCtxMock).should().getMaxRuleNodeDebugModeDurationMinutes();
then(mainCtxMock).shouldHaveNoMoreInteractions(); then(mainCtxMock).shouldHaveNoMoreInteractions();
then(tbClusterServiceMock).shouldHaveNoMoreInteractions(); then(tbClusterServiceMock).shouldHaveNoMoreInteractions();
} }
@ -655,23 +635,20 @@ class DefaultTbContextTest {
@MethodSource @MethodSource
@ParameterizedTest @ParameterizedTest
void givenDebugStrategyOptions_whenEnqueueForTellNext_thenVerifyDebugOutputPersistedOnlyForAllEventsDebugStrategy(DebugStrategy debugStrategy, String connectionType) { void givenDebugOptions_whenEnqueueForTellNext_thenVerifyDebugOutputPersistedOnlyForDebugAll(boolean debugFailures, long debugAllUntil, String connectionType) {
// GIVEN // GIVEN
var msg = getTbMsgWithQueueName(); var msg = getTbMsgWithQueueName();
var tpi = new TopicPartitionInfo(DataConstants.MAIN_QUEUE_TOPIC, TENANT_ID, 0, true); var tpi = new TopicPartitionInfo(DataConstants.MAIN_QUEUE_TOPIC, TENANT_ID, 0, true);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(debugStrategy); ruleNode.setDebugFailures(debugFailures);
ruleNode.setLastUpdateTs(System.currentTimeMillis()); ruleNode.setDebugAllUntil(debugAllUntil);
var tbClusterServiceMock = mock(TbClusterService.class); var tbClusterServiceMock = mock(TbClusterService.class);
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(mainCtxMock.resolve(any(ServiceType.class), anyString(), any(TenantId.class), any(EntityId.class))).willReturn(tpi); given(mainCtxMock.resolve(any(ServiceType.class), anyString(), any(TenantId.class), any(EntityId.class))).willReturn(tpi);
given(mainCtxMock.getClusterService()).willReturn(tbClusterServiceMock); given(mainCtxMock.getClusterService()).willReturn(tbClusterServiceMock);
if (DebugStrategy.ALL_EVENTS.equals(debugStrategy) || DebugStrategy.ALL_THEN_ONLY_FAILURE_EVENTS.equals(debugStrategy)) {
mockGetMaxRuleNodeDebugModeDurationMinutes();
}
// WHEN // WHEN
defaultTbContext.enqueueForTellNext(msg, connectionType); defaultTbContext.enqueueForTellNext(msg, connectionType);
@ -697,9 +674,7 @@ class DefaultTbContextTest {
assertThat(simpleTbQueueCallback).isNotNull(); assertThat(simpleTbQueueCallback).isNotNull();
simpleTbQueueCallback.onSuccess(null); simpleTbQueueCallback.onSuccess(null);
if (DebugStrategy.ALL_EVENTS.equals(debugStrategy) || DebugStrategy.ALL_THEN_ONLY_FAILURE_EVENTS.equals(debugStrategy)) { if (debugAllUntil > 0) {
then(mainCtxMock).should().getTenantProfileCache();
then(mainCtxMock).should().getMaxRuleNodeDebugModeDurationMinutes();
ArgumentCaptor<TbMsg> tbMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> tbMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(mainCtxMock).should().persistDebugOutput(eq(TENANT_ID), eq(RULE_NODE_ID), tbMsgCaptor.capture(), eq(connectionType), isNull(), isNull()); then(mainCtxMock).should().persistDebugOutput(eq(TENANT_ID), eq(RULE_NODE_ID), tbMsgCaptor.capture(), eq(connectionType), isNull(), isNull());
TbMsg actualTbMsg = tbMsgCaptor.getValue(); TbMsg actualTbMsg = tbMsgCaptor.getValue();
@ -714,24 +689,21 @@ class DefaultTbContextTest {
@MethodSource @MethodSource
@ParameterizedTest @ParameterizedTest
void givenDebugStrategyOptions_whenEnqueue_thenVerifyDebugOutputPersistedOnlyForAllEventsDebugStrategy(DebugStrategy debugStrategy) { void givenDebugOptions_whenEnqueue_thenVerifyDebugOutputPersistedOnlyForDebugAll(boolean debugFailures, long debugAllUntil) {
// GIVEN // GIVEN
var msg = getTbMsgWithQueueName(); var msg = getTbMsgWithQueueName();
var tpi = new TopicPartitionInfo(DataConstants.MAIN_QUEUE_TOPIC, TENANT_ID, 0, true); var tpi = new TopicPartitionInfo(DataConstants.MAIN_QUEUE_TOPIC, TENANT_ID, 0, true);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setQueueName(DataConstants.MAIN_QUEUE_NAME); ruleNode.setQueueName(DataConstants.MAIN_QUEUE_NAME);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(debugStrategy); ruleNode.setDebugFailures(debugFailures);
ruleNode.setLastUpdateTs(System.currentTimeMillis()); ruleNode.setDebugAllUntil(debugAllUntil);
var tbClusterServiceMock = mock(TbClusterService.class); var tbClusterServiceMock = mock(TbClusterService.class);
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(mainCtxMock.resolve(any(ServiceType.class), anyString(), any(TenantId.class), any(EntityId.class))).willReturn(tpi); given(mainCtxMock.resolve(any(ServiceType.class), anyString(), any(TenantId.class), any(EntityId.class))).willReturn(tpi);
given(mainCtxMock.getClusterService()).willReturn(tbClusterServiceMock); given(mainCtxMock.getClusterService()).willReturn(tbClusterServiceMock);
if (DebugStrategy.ALL_EVENTS.equals(debugStrategy) || DebugStrategy.ALL_THEN_ONLY_FAILURE_EVENTS.equals(debugStrategy)) {
mockGetMaxRuleNodeDebugModeDurationMinutes();
}
Consumer<Throwable> onFailure = mock(Consumer.class); Consumer<Throwable> onFailure = mock(Consumer.class);
Runnable onSuccess = mock(Runnable.class); Runnable onSuccess = mock(Runnable.class);
@ -760,9 +732,7 @@ class DefaultTbContextTest {
assertThat(simpleTbQueueCallback).isNotNull(); assertThat(simpleTbQueueCallback).isNotNull();
simpleTbQueueCallback.onSuccess(null); simpleTbQueueCallback.onSuccess(null);
if (debugStrategy.isHasDuration()) { if (debugAllUntil > 0) {
then(mainCtxMock).should().getTenantProfileCache();
then(mainCtxMock).should().getMaxRuleNodeDebugModeDurationMinutes();
then(mainCtxMock).should().persistDebugOutput(eq(TENANT_ID), eq(RULE_NODE_ID), eq(msg), eq(TbNodeConnectionType.TO_ROOT_RULE_CHAIN), nullable(Throwable.class), nullable(String.class)); then(mainCtxMock).should().persistDebugOutput(eq(TENANT_ID), eq(RULE_NODE_ID), eq(msg), eq(TbNodeConnectionType.TO_ROOT_RULE_CHAIN), nullable(Throwable.class), nullable(String.class));
} }
then(mainCtxMock).should().getClusterService(); then(mainCtxMock).should().getClusterService();
@ -771,12 +741,13 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenDebugStrategyOnlyFailures_whenTellFailure_thenVerifyDebugOutputPersisted() { public void givenDebugFailuress_whenTellFailure_thenVerifyDebugOutputPersisted() {
// GIVEN // GIVEN
var msg = getTbMsg(); var msg = getTbMsg();
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ONLY_FAILURE_EVENTS); ruleNode.setDebugFailures(true);
ruleNode.setDebugAllUntil(0);
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
@ -801,12 +772,13 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenDebugStrategyDisabled_whenTellFailure_thenVerifyDebugOutputNotPersisted() { public void givenDebugDisabled_whenTellFailure_thenVerifyDebugOutputNotPersisted() {
// GIVEN // GIVEN
var msg = getTbMsg(); var msg = getTbMsg();
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.DISABLED); ruleNode.setDebugFailures(false);
ruleNode.setDebugAllUntil(0);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
@ -829,17 +801,16 @@ class DefaultTbContextTest {
} }
@Test @Test
public void givenDebugStrategyAllEvents_whenTellFailure_thenVerifyDebugOutputPersisted() { public void givenDebugAllEvents_whenTellFailure_thenVerifyDebugOutputPersisted() {
// GIVEN // GIVEN
var msg = getTbMsg(); var msg = getTbMsg();
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode.setDebugFailures(false);
ruleNode.setLastUpdateTs(System.currentTimeMillis()); ruleNode.setDebugAllUntil(getUntilTime());
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
mockGetMaxRuleNodeDebugModeDurationMinutes();
// WHEN // WHEN
defaultTbContext.tellFailure(msg, EXCEPTION); defaultTbContext.tellFailure(msg, EXCEPTION);
@ -856,15 +827,14 @@ class DefaultTbContextTest {
then(chainActorMock).shouldHaveNoMoreInteractions(); then(chainActorMock).shouldHaveNoMoreInteractions();
then(nodeCtxMock).should().getChainActor(); then(nodeCtxMock).should().getChainActor();
then(mainCtxMock).should().persistDebugOutput(TENANT_ID, RULE_NODE_ID, msg, TbNodeConnectionType.FAILURE, EXCEPTION, null); then(mainCtxMock).should().persistDebugOutput(TENANT_ID, RULE_NODE_ID, msg, TbNodeConnectionType.FAILURE, EXCEPTION, null);
then(mainCtxMock).should().getTenantProfileCache();
then(mainCtxMock).should().getMaxRuleNodeDebugModeDurationMinutes();
then(mainCtxMock).shouldHaveNoMoreInteractions(); then(mainCtxMock).shouldHaveNoMoreInteractions();
then(nodeCtxMock).shouldHaveNoMoreInteractions(); then(nodeCtxMock).shouldHaveNoMoreInteractions();
} }
@MethodSource @MethodSource
@ParameterizedTest @ParameterizedTest
void givenDebugStrategyAndConnectionAndPersistedResultOptions_whenTellNext_thenVerifyDebugOutputPersistence(DebugStrategy debugStrategy, void givenDebugFailuresAndDebugAllAndConnectionAndPersistedResultOptions_whenTellNext_thenVerifyDebugOutputPersistence(boolean debugFailures,
long debugAllUntil,
String connection, String connection,
boolean shouldPersist, boolean shouldPersist,
boolean shouldPersistAfterDurationTime) { boolean shouldPersistAfterDurationTime) {
@ -873,16 +843,13 @@ class DefaultTbContextTest {
var msg = getTbMsgWithCallback(callbackMock); var msg = getTbMsgWithCallback(callbackMock);
var ruleNode = new RuleNode(RULE_NODE_ID); var ruleNode = new RuleNode(RULE_NODE_ID);
ruleNode.setRuleChainId(RULE_CHAIN_ID); ruleNode.setRuleChainId(RULE_CHAIN_ID);
ruleNode.setLastUpdateTs(System.currentTimeMillis()); ruleNode.setDebugFailures(debugFailures);
ruleNode.setDebugStrategy(debugStrategy); ruleNode.setDebugAllUntil(debugAllUntil);
if (shouldPersist) { if (shouldPersist) {
given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID); given(nodeCtxMock.getTenantId()).willReturn(TENANT_ID);
} }
given(nodeCtxMock.getSelf()).willReturn(ruleNode); given(nodeCtxMock.getSelf()).willReturn(ruleNode);
given(nodeCtxMock.getChainActor()).willReturn(chainActorMock); given(nodeCtxMock.getChainActor()).willReturn(chainActorMock);
if (debugStrategy.isHasDuration()) {
mockGetMaxRuleNodeDebugModeDurationMinutes();
}
// WHEN // WHEN
defaultTbContext.tellNext(msg, connection); defaultTbContext.tellNext(msg, connection);
@ -894,9 +861,7 @@ class DefaultTbContextTest {
// GIVEN // GIVEN
Mockito.clearInvocations(mainCtxMock); Mockito.clearInvocations(mainCtxMock);
if (debugStrategy.isHasDuration()) { ruleNode.setDebugAllUntil(0);
mockGetMaxRuleNodeDebugModeDurationMinutes(0);
}
// WHEN // WHEN
defaultTbContext.tellNext(msg, connection); defaultTbContext.tellNext(msg, connection);
@ -956,34 +921,34 @@ class DefaultTbContextTest {
simpleTbQueueCallback.onSuccess(null); simpleTbQueueCallback.onSuccess(null);
} }
private static Stream<Arguments> givenDebugStrategyOptions_whenEnqueueForTellNext_thenVerifyDebugOutputPersistedOnlyForAllEventsDebugStrategy() { private static Stream<Arguments> givenDebugOptions_whenEnqueueForTellNext_thenVerifyDebugOutputPersistedOnlyForDebugAll() {
return Stream.of( return Stream.of(
Arguments.of(DebugStrategy.ALL_EVENTS, TbNodeConnectionType.OTHER), Arguments.of(false, getUntilTime(), TbNodeConnectionType.OTHER),
Arguments.of(DebugStrategy.ALL_THEN_ONLY_FAILURE_EVENTS, TbNodeConnectionType.OTHER), Arguments.of(true, getUntilTime(), TbNodeConnectionType.OTHER),
Arguments.of(DebugStrategy.ONLY_FAILURE_EVENTS, TbNodeConnectionType.TRUE), Arguments.of(true, 0, TbNodeConnectionType.TRUE),
Arguments.of(DebugStrategy.DISABLED, TbNodeConnectionType.FALSE) Arguments.of(false, 0, TbNodeConnectionType.FALSE)
); );
} }
private static Stream<Arguments> givenDebugStrategyOptions_whenEnqueue_thenVerifyDebugOutputPersistedOnlyForAllEventsDebugStrategy() { private static Stream<Arguments> givenDebugOptions_whenEnqueue_thenVerifyDebugOutputPersistedOnlyForDebugAll() {
return Stream.of( return Stream.of(
Arguments.of(DebugStrategy.ALL_EVENTS), Arguments.of(false, getUntilTime()),
Arguments.of(DebugStrategy.ALL_THEN_ONLY_FAILURE_EVENTS), Arguments.of(true, getUntilTime()),
Arguments.of(DebugStrategy.ONLY_FAILURE_EVENTS), Arguments.of(true, 0),
Arguments.of(DebugStrategy.DISABLED) Arguments.of(false, 0)
); );
} }
private static Stream<Arguments> givenDebugStrategyAndConnectionAndPersistedResultOptions_whenTellNext_thenVerifyDebugOutputPersistence() { private static Stream<Arguments> givenDebugFailuresAndDebugAllAndConnectionAndPersistedResultOptions_whenTellNext_thenVerifyDebugOutputPersistence() {
return Stream.of( return Stream.of(
Arguments.of(DebugStrategy.ALL_EVENTS, TbNodeConnectionType.SUCCESS, true, false), Arguments.of(false, getUntilTime(), TbNodeConnectionType.SUCCESS, true, false),
Arguments.of(DebugStrategy.ALL_EVENTS, TbNodeConnectionType.FAILURE, true, false), Arguments.of(false, getUntilTime(), TbNodeConnectionType.FAILURE, true, false),
Arguments.of(DebugStrategy.ALL_THEN_ONLY_FAILURE_EVENTS, TbNodeConnectionType.SUCCESS, true, false), Arguments.of(true, getUntilTime(), TbNodeConnectionType.SUCCESS, true, false),
Arguments.of(DebugStrategy.ALL_THEN_ONLY_FAILURE_EVENTS, TbNodeConnectionType.FAILURE, true, true), Arguments.of(true, getUntilTime(), TbNodeConnectionType.FAILURE, true, true),
Arguments.of(DebugStrategy.ONLY_FAILURE_EVENTS, TbNodeConnectionType.SUCCESS, false, false), Arguments.of(true, 0, TbNodeConnectionType.SUCCESS, false, false),
Arguments.of(DebugStrategy.ONLY_FAILURE_EVENTS, TbNodeConnectionType.FAILURE, true, true), Arguments.of(true, 0, TbNodeConnectionType.FAILURE, true, true),
Arguments.of(DebugStrategy.DISABLED, TbNodeConnectionType.SUCCESS, false, false), Arguments.of(false, 0, TbNodeConnectionType.SUCCESS, false, false),
Arguments.of(DebugStrategy.DISABLED, TbNodeConnectionType.FAILURE, false, false) Arguments.of(false, 0, TbNodeConnectionType.FAILURE, false, false)
); );
} }
@ -999,21 +964,11 @@ class DefaultTbContextTest {
return TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TENANT_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); return TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TENANT_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING);
} }
private void mockGetMaxRuleNodeDebugModeDurationMinutes() { private static long getUntilTime() {
mockGetMaxRuleNodeDebugModeDurationMinutes(15); return getUntilTime(15);
} }
private void mockGetMaxRuleNodeDebugModeDurationMinutes(int maxRuleNodeDebugModeDurationMinutes) { private static long getUntilTime(int maxRuleNodeDebugModeDurationMinutes) {
var tbTenantProfileCacheMock = mock(TbTenantProfileCache.class); return System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(maxRuleNodeDebugModeDurationMinutes);
var tenantProfileMock = mock(TenantProfile.class);
var tenantProfileDataMock = mock(TenantProfileData.class);
var tenantProfileConfigurationMock = mock(TenantProfileConfiguration.class);
given(mainCtxMock.getTenantProfileCache()).willReturn(tbTenantProfileCacheMock);
given(tbTenantProfileCacheMock.get(TENANT_ID)).willReturn(tenantProfileMock);
given(tenantProfileMock.getProfileData()).willReturn(tenantProfileDataMock);
given(tenantProfileDataMock.getConfiguration()).willReturn(tenantProfileConfigurationMock);
given(tenantProfileConfigurationMock.getMaxRuleNodeDebugModeDurationMinutes(anyInt())).willReturn(maxRuleNodeDebugModeDurationMinutes);
} }
} }

6
application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java

@ -71,7 +71,6 @@ import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.rule.DebugStrategy;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
@ -164,7 +163,8 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
} }
private RuleChainId getEdgeRootRuleChainId() throws Exception { private RuleChainId getEdgeRootRuleChainId() throws Exception {
return doGetTypedWithPageLink("/api/ruleChains?type={type}&", new TypeReference<PageData<RuleChain>>() {}, return doGetTypedWithPageLink("/api/ruleChains?type={type}&", new TypeReference<PageData<RuleChain>>() {
},
new PageLink(100, 0, "Edge Root Rule Chain"), new PageLink(100, 0, "Edge Root Rule Chain"),
"EDGE") "EDGE")
.getData().get(0).getId(); .getData().get(0).getId();
@ -208,7 +208,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
protected void updateRootRuleChainMetadata() throws Exception { protected void updateRootRuleChainMetadata() throws Exception {
RuleChainId rootRuleChainId = getEdgeRootRuleChainId(); RuleChainId rootRuleChainId = getEdgeRootRuleChainId();
RuleChainMetaData rootRuleChainMetadata = doGet("/api/ruleChain/" + rootRuleChainId.getId().toString() + "/metadata", RuleChainMetaData.class); RuleChainMetaData rootRuleChainMetadata = doGet("/api/ruleChain/" + rootRuleChainId.getId().toString() + "/metadata", RuleChainMetaData.class);
rootRuleChainMetadata.getNodes().forEach(n -> n.setDebugStrategy(DebugStrategy.ALL_EVENTS)); rootRuleChainMetadata.getNodes().forEach(n -> n.setDebugAll(true));
doPost("/api/ruleChain/metadata", rootRuleChainMetadata, RuleChainMetaData.class); doPost("/api/ruleChain/metadata", rootRuleChainMetadata, RuleChainMetaData.class);
} }

3
application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java

@ -23,7 +23,6 @@ import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.rule.engine.util.TbMsgSource;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.DebugStrategy;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
@ -229,7 +228,7 @@ public class RuleChainEdgeTest extends AbstractEdgeTest {
// update metadata for root rule chain // update metadata for root rule chain
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
metaData.getNodes().forEach(n -> n.setDebugStrategy(DebugStrategy.ALL_EVENTS)); metaData.getNodes().forEach(n -> n.setDebugAll(true));
doPost("/api/ruleChain/metadata", metaData, RuleChainMetaData.class); doPost("/api/ruleChain/metadata", metaData, RuleChainMetaData.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);

11
application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java

@ -43,7 +43,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.rule.DebugStrategy;
import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@ -143,7 +142,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
ruleNode1.setName("Simple Rule Node 1"); ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode1.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode1.setDebugAll(true);
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
configuration1.setFetchTo(TbMsgSource.METADATA); configuration1.setFetchTo(TbMsgSource.METADATA);
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
@ -153,7 +152,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
ruleNode2.setName("Simple Rule Node 2"); ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode2.setDebugAll(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setFetchTo(TbMsgSource.METADATA); configuration2.setFetchTo(TbMsgSource.METADATA);
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
@ -249,7 +248,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
ruleNode1.setName("Simple Rule Node 1"); ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode1.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode1.setDebugAll(true);
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
configuration1.setFetchTo(TbMsgSource.METADATA); configuration1.setFetchTo(TbMsgSource.METADATA);
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
@ -258,7 +257,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
RuleNode ruleNode12 = new RuleNode(); RuleNode ruleNode12 = new RuleNode();
ruleNode12.setName("Simple Rule Node 1"); ruleNode12.setName("Simple Rule Node 1");
ruleNode12.setType(org.thingsboard.rule.engine.flow.TbRuleChainInputNode.class.getName()); ruleNode12.setType(org.thingsboard.rule.engine.flow.TbRuleChainInputNode.class.getName());
ruleNode12.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode12.setDebugAll(true);
TbRuleChainInputNodeConfiguration configuration12 = new TbRuleChainInputNodeConfiguration(); TbRuleChainInputNodeConfiguration configuration12 = new TbRuleChainInputNodeConfiguration();
configuration12.setRuleChainId(secondaryRuleChain.getId().getId().toString()); configuration12.setRuleChainId(secondaryRuleChain.getId().getId().toString());
ruleNode12.setConfiguration(JacksonUtil.valueToTree(configuration12)); ruleNode12.setConfiguration(JacksonUtil.valueToTree(configuration12));
@ -283,7 +282,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
ruleNode2.setName("Simple Rule Node 2"); ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode2.setDebugAll(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setFetchTo(TbMsgSource.METADATA); configuration2.setFetchTo(TbMsgSource.METADATA);
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));

3
application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java

@ -36,7 +36,6 @@ import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.rule.DebugStrategy;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
@ -98,7 +97,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
ruleNode.setName("Simple Rule Node"); ruleNode.setName("Simple Rule Node");
ruleNode.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode.setDebugAll(true);
TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration();
configuration.setFetchTo(TbMsgSource.METADATA); configuration.setFetchTo(TbMsgSource.METADATA);
configuration.setServerAttributeNames(Collections.singletonList("serverAttributeKey")); configuration.setServerAttributeNames(Collections.singletonList("serverAttributeKey"));

5
application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java

@ -58,7 +58,6 @@ import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.DebugStrategy;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
@ -463,7 +462,7 @@ public class HousekeeperServiceTest extends AbstractControllerTest {
ruleNode1.setName("Simple Rule Node 1"); ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode1.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode1.setDebugAll(true);
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1)); ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
@ -472,7 +471,7 @@ public class HousekeeperServiceTest extends AbstractControllerTest {
ruleNode2.setName("Simple Rule Node 2"); ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode2.setDebugAll(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2)); ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));

9
application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java

@ -65,7 +65,6 @@ import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.DebugStrategy;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
@ -472,7 +471,7 @@ public class ExportImportServiceSqlTest extends AbstractControllerTest {
RuleNode ruleNode1 = new RuleNode(); RuleNode ruleNode1 = new RuleNode();
ruleNode1.setName("Generator 1"); ruleNode1.setName("Generator 1");
ruleNode1.setType(TbMsgGeneratorNode.class.getName()); ruleNode1.setType(TbMsgGeneratorNode.class.getName());
ruleNode1.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode1.setDebugAllUntil(System.currentTimeMillis());
TbMsgGeneratorNodeConfiguration configuration1 = new TbMsgGeneratorNodeConfiguration(); TbMsgGeneratorNodeConfiguration configuration1 = new TbMsgGeneratorNodeConfiguration();
configuration1.setOriginatorType(originatorId.getEntityType()); configuration1.setOriginatorType(originatorId.getEntityType());
configuration1.setOriginatorId(originatorId.getId().toString()); configuration1.setOriginatorId(originatorId.getId().toString());
@ -482,7 +481,7 @@ public class ExportImportServiceSqlTest extends AbstractControllerTest {
ruleNode2.setName("Simple Rule Node 2"); ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode2.setDebugAllUntil(System.currentTimeMillis());
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2)); ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));
@ -511,7 +510,7 @@ public class ExportImportServiceSqlTest extends AbstractControllerTest {
ruleNode1.setName("Simple Rule Node 1"); ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode1.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode1.setDebugAllUntil(System.currentTimeMillis());
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1)); ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
@ -520,7 +519,7 @@ public class ExportImportServiceSqlTest extends AbstractControllerTest {
ruleNode2.setName("Simple Rule Node 2"); ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode2.setDebugAllUntil(System.currentTimeMillis());
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2)); ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));

9
application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java

@ -65,7 +65,6 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.DebugStrategy;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
@ -871,7 +870,7 @@ public class VersionControlTest extends AbstractControllerTest {
RuleNode ruleNode1 = new RuleNode(); RuleNode ruleNode1 = new RuleNode();
ruleNode1.setName("Generator 1"); ruleNode1.setName("Generator 1");
ruleNode1.setType(TbMsgGeneratorNode.class.getName()); ruleNode1.setType(TbMsgGeneratorNode.class.getName());
ruleNode1.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode1.setDebugAll(true);
TbMsgGeneratorNodeConfiguration configuration1 = new TbMsgGeneratorNodeConfiguration(); TbMsgGeneratorNodeConfiguration configuration1 = new TbMsgGeneratorNodeConfiguration();
configuration1.setOriginatorType(originatorId.getEntityType()); configuration1.setOriginatorType(originatorId.getEntityType());
configuration1.setOriginatorId(originatorId.getId().toString()); configuration1.setOriginatorId(originatorId.getId().toString());
@ -881,7 +880,7 @@ public class VersionControlTest extends AbstractControllerTest {
ruleNode2.setName("Simple Rule Node 2"); ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode2.setDebugAll(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2)); ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));
@ -909,7 +908,7 @@ public class VersionControlTest extends AbstractControllerTest {
ruleNode1.setName("Simple Rule Node 1"); ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode1.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode1.setDebugAll(true);
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1)); ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
@ -918,7 +917,7 @@ public class VersionControlTest extends AbstractControllerTest {
ruleNode2.setName("Simple Rule Node 2"); ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugStrategy(DebugStrategy.ALL_EVENTS); ruleNode2.setDebugAll(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2)); ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));

33
common/data/src/main/java/org/thingsboard/server/common/data/HasDebugMode.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
import io.swagger.v3.oas.annotations.media.Schema;
public interface HasDebugMode {
boolean isDebugFailures();
void setDebugFailures(boolean debugFailures);
boolean isDebugAll();
void setDebugAll(boolean debugAll);
long getDebugAllUntil();
void setDebugAllUntil(long debugAllUntil);
}

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

@ -32,6 +32,6 @@ public class SystemParams {
long maxDatapointsLimit; long maxDatapointsLimit;
long maxResourceSize; long maxResourceSize;
boolean mobileQrEnabled; boolean mobileQrEnabled;
int maxRuleNodeDebugDurationMinutes; int maxDebugModeDurationMinutes;
String ruleChainDebugPerTenantLimitsConfiguration; String ruleChainDebugPerTenantLimitsConfiguration;
} }

70
common/data/src/main/java/org/thingsboard/server/common/data/rule/DebugStrategy.java

@ -1,70 +0,0 @@
/**
* Copyright © 2016-2024 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.rule;
import lombok.Getter;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Getter
public enum DebugStrategy {
DISABLED(0, false),
ALL_EVENTS(1, true),
ALL_THEN_ONLY_FAILURE_EVENTS(2, true),
ONLY_FAILURE_EVENTS(3, false);
private final int protoNumber;
private final boolean hasDuration;
DebugStrategy(int protoNumber, boolean hasDuration) {
this.protoNumber = protoNumber;
this.hasDuration = hasDuration;
}
public boolean shouldPersistDebugInput(long lastUpdateTs, long msgTs, int debugModeDurationMinutes) {
return isAllEventsStrategyAndMsgTsWithinDebugDuration(lastUpdateTs, msgTs, debugModeDurationMinutes);
}
public boolean shouldPersistDebugOutputForAllEvents(long lastUpdateTs, long msgTs, int debugModeDurationMinutes) {
return this.isAllEventsStrategyAndMsgTsWithinDebugDuration(lastUpdateTs, msgTs, debugModeDurationMinutes);
}
public boolean shouldPersistDebugForFailureEvent(Set<String> nodeConnections) {
return isFailureStrategy() && nodeConnections.contains(TbNodeConnectionType.FAILURE);
}
public boolean shouldPersistDebugForFailureEvent(String nodeConnection) {
return isFailureStrategy() && TbNodeConnectionType.FAILURE.equals(nodeConnection);
}
private boolean isFailureStrategy() {
return DebugStrategy.ONLY_FAILURE_EVENTS.equals(this) || DebugStrategy.ALL_THEN_ONLY_FAILURE_EVENTS.equals(this);
}
private boolean isAllEventsStrategyAndMsgTsWithinDebugDuration(long lastUpdateTs, long msgTs, int debugModeDurationMinutes) {
return this.hasDuration && isMsgTsWithinDebugDuration(lastUpdateTs, msgTs, debugModeDurationMinutes);
}
private boolean isMsgTsWithinDebugDuration(long lastUpdateTs, long msgCreationTs, int debugModeDurationMinutes) {
if (debugModeDurationMinutes <= 0) {
return true;
}
return msgCreationTs < lastUpdateTs + TimeUnit.MINUTES.toMillis(debugModeDurationMinutes);
}
}

22
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java

@ -23,6 +23,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo; import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo;
import org.thingsboard.server.common.data.HasDebugMode;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.RuleNodeId;
@ -34,7 +35,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Slf4j @Slf4j
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class RuleNode extends BaseDataWithAdditionalInfo<RuleNodeId> implements HasName { public class RuleNode extends BaseDataWithAdditionalInfo<RuleNodeId> implements HasName, HasDebugMode {
private static final long serialVersionUID = -5656679015121235465L; private static final long serialVersionUID = -5656679015121235465L;
@ -47,10 +48,12 @@ public class RuleNode extends BaseDataWithAdditionalInfo<RuleNodeId> implements
@Length(fieldName = "name") @Length(fieldName = "name")
@Schema(description = "User defined name of the rule node. Used on UI and for logging. ", example = "Process sensor reading") @Schema(description = "User defined name of the rule node. Used on UI and for logging. ", example = "Process sensor reading")
private String name; private String name;
@Schema(description = "Timestamp of the last rule node update.") @Schema(description = "Debug failures. ", example = "false")
private long lastUpdateTs; private boolean debugFailures;
@Schema(description = "Debug strategy. ", example = "ALL_EVENTS") @Schema(description = "Debug All. Used as a trigger for updating debugAllUntil.", example = "false")
private DebugStrategy debugStrategy; private boolean debugAll;
@Schema(description = "Timestamp of the end time for the processing debug events.")
private long debugAllUntil;
@Schema(description = "Enable/disable singleton mode. ", example = "false") @Schema(description = "Enable/disable singleton mode. ", example = "false")
private boolean singletonMode; private boolean singletonMode;
@Schema(description = "Queue name. ", example = "Main") @Schema(description = "Queue name. ", example = "Main")
@ -77,8 +80,9 @@ public class RuleNode extends BaseDataWithAdditionalInfo<RuleNodeId> implements
this.ruleChainId = ruleNode.getRuleChainId(); this.ruleChainId = ruleNode.getRuleChainId();
this.type = ruleNode.getType(); this.type = ruleNode.getType();
this.name = ruleNode.getName(); this.name = ruleNode.getName();
this.lastUpdateTs = ruleNode.getLastUpdateTs(); this.debugFailures = ruleNode.isDebugFailures();
this.debugStrategy = ruleNode.getDebugStrategy(); this.debugAll = ruleNode.isDebugAll();
this.debugAllUntil = ruleNode.getDebugAllUntil();
this.singletonMode = ruleNode.isSingletonMode(); this.singletonMode = ruleNode.isSingletonMode();
this.setConfiguration(ruleNode.getConfiguration()); this.setConfiguration(ruleNode.getConfiguration());
this.externalId = ruleNode.getExternalId(); this.externalId = ruleNode.getExternalId();
@ -89,10 +93,6 @@ public class RuleNode extends BaseDataWithAdditionalInfo<RuleNodeId> implements
return name; return name;
} }
public DebugStrategy getDebugStrategy() {
return debugStrategy == null ? DebugStrategy.DISABLED : debugStrategy;
}
public JsonNode getConfiguration() { public JsonNode getConfiguration() {
return BaseDataWithAdditionalInfo.getJson(() -> configuration, () -> configurationBytes); return BaseDataWithAdditionalInfo.getJson(() -> configuration, () -> configurationBytes);
} }

9
common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java

@ -95,7 +95,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
@Schema(example = "50") @Schema(example = "50")
private int maxRuleNodeExecutionsPerMessage; private int maxRuleNodeExecutionsPerMessage;
@Schema(example = "15") @Schema(example = "15")
private int maxRuleNodeDebugDurationMinutes; private int maxDebugModeDurationMinutes;
@Schema(example = "0") @Schema(example = "0")
private long maxEmails; private long maxEmails;
@Schema(example = "true") @Schema(example = "true")
@ -204,7 +204,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
} }
@Override @Override
public int getMaxRuleNodeDebugModeDurationMinutes(int systemMaxRuleNodeDebugModeDurationMinutes) { public int getMaxDebugModeDurationMinutes(int systemMaxDebugModeDurationMinutes) {
return Math.min(systemMaxRuleNodeDebugModeDurationMinutes, maxRuleNodeDebugDurationMinutes); if (maxDebugModeDurationMinutes > 0) {
return Math.min(systemMaxDebugModeDurationMinutes, maxDebugModeDurationMinutes);
}
return systemMaxDebugModeDurationMinutes;
} }
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java

@ -52,6 +52,6 @@ public interface TenantProfileConfiguration extends Serializable {
int getMaxRuleNodeExecsPerMessage(); int getMaxRuleNodeExecsPerMessage();
@JsonIgnore @JsonIgnore
int getMaxRuleNodeDebugModeDurationMinutes(int systemMaxRuleNodeDebugModeDurationMinutes); int getMaxDebugModeDurationMinutes(int systemMaxDebugModeDurationMinutes);
} }

11
common/edge-api/src/main/proto/edge.proto

@ -162,13 +162,6 @@ message RuleChainMetadataUpdateMsg {
string entity = 8; string entity = 8;
} }
enum DebugStrategy {
DISABLED = 0;
ALL_EVENTS = 1;
ALL_THEN_ONLY_FAILURE_EVENTS = 2;
ONLY_FAILURE_EVENTS = 3;
}
message RuleNodeProto { message RuleNodeProto {
option deprecated = true; option deprecated = true;
int64 idMSB = 1; int64 idMSB = 1;
@ -180,8 +173,8 @@ message RuleNodeProto {
string additionalInfo = 7; string additionalInfo = 7;
bool singletonMode = 8; bool singletonMode = 8;
int32 configurationVersion = 9; int32 configurationVersion = 9;
int64 lastUpdateTs = 10; bool debugFailures = 10;
DebugStrategy debugStrategy = 11; int64 debugAllUntil = 11;
} }
message NodeConnectionInfoProto { message NodeConnectionInfoProto {

43
common/util/src/main/java/org/thingsboard/common/util/DebugModeUtil.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2024 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.common.util;
import org.thingsboard.server.common.data.HasDebugMode;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import java.util.Set;
public final class DebugModeUtil {
private DebugModeUtil() {
}
public static boolean isDebugAllAvailable(HasDebugMode debugMode) {
return debugMode.getDebugAllUntil() > System.currentTimeMillis();
}
public static boolean isDebugAvailable(HasDebugMode debugMode, String nodeConnection) {
return isDebugAllAvailable(debugMode) || debugMode.isDebugFailures() && TbNodeConnectionType.FAILURE.equals(nodeConnection);
}
public static boolean isDebugFailuresAvailable(HasDebugMode debugMode, Set<String> nodeConnections) {
return isDebugFailuresAvailable(debugMode) && nodeConnections.contains(TbNodeConnectionType.FAILURE);
}
private static boolean isDebugFailuresAvailable(HasDebugMode debugMode) {
return debugMode.isDebugFailures() || isDebugAllAvailable(debugMode);
}
}

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

@ -398,7 +398,8 @@ public class ModelConstants {
public static final String EVENT_MESSAGE_COLUMN_NAME = "e_message"; public static final String EVENT_MESSAGE_COLUMN_NAME = "e_message";
public static final String DEBUG_MODE = "debug_mode"; public static final String DEBUG_MODE = "debug_mode";
public static final String DEBUG_STRATEGY = "debug_strategy"; public static final String DEBUG_FAILURES = "debug_failures";
public static final String DEBUG__ALL_UNTIL = "debug_all_until";
public static final String SINGLETON_MODE = "singleton_mode"; public static final String SINGLETON_MODE = "singleton_mode";
public static final String QUEUE_NAME = "queue_name"; public static final String QUEUE_NAME = "queue_name";

20
dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java

@ -19,14 +19,11 @@ import com.fasterxml.jackson.databind.JsonNode;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Convert; import jakarta.persistence.Convert;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.rule.DebugStrategy;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.BaseSqlEntity;
@ -61,12 +58,11 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> {
@Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY) @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo; private JsonNode additionalInfo;
@Column(name = ModelConstants.LAST_UPDATE_TS_COLUMN) @Column(name = ModelConstants.DEBUG_FAILURES)
private long lastUpdateTs; private boolean debugFailures;
@Enumerated(EnumType.STRING) @Column(name = ModelConstants.DEBUG__ALL_UNTIL)
@Column(name = ModelConstants.DEBUG_STRATEGY) private long debugAllUntil;
private DebugStrategy debugStrategy;
@Column(name = ModelConstants.SINGLETON_MODE) @Column(name = ModelConstants.SINGLETON_MODE)
private boolean singletonMode; private boolean singletonMode;
@ -90,8 +86,8 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> {
} }
this.type = ruleNode.getType(); this.type = ruleNode.getType();
this.name = ruleNode.getName(); this.name = ruleNode.getName();
this.lastUpdateTs = ruleNode.getLastUpdateTs(); this.debugFailures = ruleNode.isDebugFailures();
this.debugStrategy = ruleNode.getDebugStrategy(); this.debugAllUntil = ruleNode.getDebugAllUntil();
this.singletonMode = ruleNode.isSingletonMode(); this.singletonMode = ruleNode.isSingletonMode();
this.queueName = ruleNode.getQueueName(); this.queueName = ruleNode.getQueueName();
this.configurationVersion = ruleNode.getConfigurationVersion(); this.configurationVersion = ruleNode.getConfigurationVersion();
@ -111,8 +107,8 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> {
} }
ruleNode.setType(type); ruleNode.setType(type);
ruleNode.setName(name); ruleNode.setName(name);
ruleNode.setLastUpdateTs(lastUpdateTs); ruleNode.setDebugFailures(debugFailures);
ruleNode.setDebugStrategy(debugStrategy); ruleNode.setDebugAllUntil(debugAllUntil);
ruleNode.setSingletonMode(singletonMode); ruleNode.setSingletonMode(singletonMode);
ruleNode.setQueueName(queueName); ruleNode.setQueueName(queueName);
ruleNode.setConfigurationVersion(configurationVersion); ruleNode.setConfigurationVersion(configurationVersion);

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

@ -376,7 +376,8 @@ public class DefaultNotifications {
.name("Queue type deprecation") .name("Queue type deprecation")
.type(NotificationType.GENERAL) .type(NotificationType.GENERAL)
.subject("WARNING: ${queueType} deprecation") .subject("WARNING: ${queueType} deprecation")
.text("${queueType} queue type is deprecated and will be removed in ThingsBoard 4.0. Please migrate to Apache Kafka") .text("Starting with ThingsBoard 4.0, ${queueType} will no longer be supported as a message queue for microservices. " +
"Please migrate to Apache Kafka. This change will not impact any rule nodes.")
.icon("warning").color(RED_COLOR) .icon("warning").color(RED_COLOR)
.build(); .build();

26
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -24,6 +24,8 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
@ -44,7 +46,6 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentClusteringMode; import org.thingsboard.server.common.data.plugin.ComponentClusteringMode;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.DebugStrategy;
import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
@ -66,6 +67,7 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.service.validator.RuleChainDataValidator; import org.thingsboard.server.dao.service.validator.RuleChainDataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -77,6 +79,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -110,6 +113,13 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
@Autowired @Autowired
private DataValidator<RuleChain> ruleChainValidator; private DataValidator<RuleChain> ruleChainValidator;
@Autowired
@Lazy
private TbTenantProfileCache tbTenantProfileCache;
@Value("${debug_mode.max_duration:60}")
private int maxDebugModeDurationMinutes;
@Override @Override
@Transactional @Transactional
public RuleChain saveRuleChain(RuleChain ruleChain) { public RuleChain saveRuleChain(RuleChain ruleChain) {
@ -215,11 +225,20 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
} }
RuleChainId ruleChainId = ruleChain.getId(); RuleChainId ruleChainId = ruleChain.getId();
if (nodes != null) { if (nodes != null) {
long lastUpdateTs = System.currentTimeMillis(); long now = System.currentTimeMillis();
for (RuleNode node : toAddOrUpdate) { for (RuleNode node : toAddOrUpdate) {
node.setRuleChainId(ruleChainId); node.setRuleChainId(ruleChainId);
node = ruleNodeUpdater.apply(node); node = ruleNodeUpdater.apply(node);
node.setLastUpdateTs(lastUpdateTs);
int debugDuration = tbTenantProfileCache.get(tenantId).getDefaultProfileConfiguration().getMaxDebugModeDurationMinutes(maxDebugModeDurationMinutes);
long debugUntil = now + TimeUnit.MINUTES.toMillis(debugDuration);
if (node.isDebugAll()) {
node.setDebugAllUntil(debugUntil);
} else if (node.getDebugAllUntil() > debugUntil) {
throw new DataValidationException("Unable to update 'debugAllUntil' property. To reset the debug duration, please modify the 'debugAll' property instead.");
}
RuleChainDataValidator.validateRuleNode(node); RuleChainDataValidator.validateRuleNode(node);
RuleNode savedNode = ruleNodeDao.save(tenantId, node); RuleNode savedNode = ruleNodeDao.save(tenantId, node);
relations.add(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(), relations.add(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(),
@ -264,7 +283,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
layout.remove("description"); layout.remove("description");
layout.remove("ruleChainNodeId"); layout.remove("ruleChainNodeId");
targetNode.setAdditionalInfo(layout); targetNode.setAdditionalInfo(layout);
targetNode.setDebugStrategy(DebugStrategy.DISABLED);
targetNode = ruleNodeDao.save(tenantId, targetNode); targetNode = ruleNodeDao.save(tenantId, targetNode);
EntityRelation sourceRuleChainToRuleNode = new EntityRelation(); EntityRelation sourceRuleChainToRuleNode = new EntityRelation();

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

@ -181,8 +181,8 @@ CREATE TABLE IF NOT EXISTS rule_node (
configuration varchar(10000000), configuration varchar(10000000),
type varchar(255), type varchar(255),
name varchar(255), name varchar(255),
last_update_ts bigint NOT NULL, debug_failures boolean,
debug_strategy varchar(32) DEFAULT 'DISABLED', debug_all_until bigint NOT NULL,
singleton_mode boolean, singleton_mode boolean,
queue_name varchar(255), queue_name varchar(255),
external_id uuid external_id uuid

7
dao/src/test/java/org/thingsboard/server/dao/service/timeseries/sql/LatestTimeseriesPerformanceTest.java

@ -56,7 +56,8 @@ public class LatestTimeseriesPerformanceTest extends AbstractServiceTest {
private static final String LONG_KEY = "longKey"; private static final String LONG_KEY = "longKey";
private static final String DOUBLE_KEY = "doubleKey"; private static final String DOUBLE_KEY = "doubleKey";
private static final String BOOLEAN_KEY = "booleanKey"; private static final String BOOLEAN_KEY = "booleanKey";
public static final int AMOUNT_OF_UNIQ_KEY = 10000; private static final int AMOUNT_OF_UNIQ_KEY = 10000;
private static final int TIMEOUT = 100;
private final Random random = new Random(); private final Random random = new Random();
@ -102,7 +103,7 @@ public class LatestTimeseriesPerformanceTest extends AbstractServiceTest {
futures.add(save(generateDblEntry(getRandomKey()))); futures.add(save(generateDblEntry(getRandomKey())));
futures.add(save(generateBoolEntry(getRandomKey()))); futures.add(save(generateBoolEntry(getRandomKey())));
} }
Futures.allAsList(futures).get(60, TimeUnit.SECONDS); Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
long endTime = System.currentTimeMillis(); long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime; long totalTime = endTime - startTime;
@ -120,7 +121,7 @@ public class LatestTimeseriesPerformanceTest extends AbstractServiceTest {
futures.add(save(generateDblEntry(i))); futures.add(save(generateDblEntry(i)));
futures.add(save(generateBoolEntry(i))); futures.add(save(generateBoolEntry(i)));
} }
Futures.allAsList(futures).get(60, TimeUnit.SECONDS); Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
} }
private ListenableFuture<?> save(TsKvEntry tsKvEntry) { private ListenableFuture<?> save(TsKvEntry tsKvEntry) {

54
monitoring/src/main/resources/root_rule_chain.json

@ -20,7 +20,8 @@
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries", "name": "Save Timeseries",
"debugStrategy": "ALL_EVENTS", "debugFailures": false,
"debugAll": true,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -35,7 +36,8 @@
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Attributes", "name": "Save Attributes",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 1, "configurationVersion": 1,
"configuration": { "configuration": {
@ -53,7 +55,8 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch", "name": "Message Type Switch",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -68,7 +71,8 @@
}, },
"type": "org.thingsboard.rule.engine.action.TbLogNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device", "name": "Log RPC from Device",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -85,7 +89,8 @@
}, },
"type": "org.thingsboard.rule.engine.action.TbLogNode", "type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other", "name": "Log Other",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -102,7 +107,8 @@
}, },
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request", "name": "RPC Call Request",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -117,7 +123,8 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbOriginatorTypeFilterNode", "type": "org.thingsboard.rule.engine.filter.TbOriginatorTypeFilterNode",
"name": "Is Entity Group", "name": "Is Entity Group",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -134,7 +141,8 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeFilterNode", "type": "org.thingsboard.rule.engine.filter.TbMsgTypeFilterNode",
"name": "Post attributes or RPC request", "name": "Post attributes or RPC request",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -152,7 +160,8 @@
}, },
"type": "org.thingsboard.rule.engine.transform.TbDuplicateMsgToGroupNode", "type": "org.thingsboard.rule.engine.transform.TbDuplicateMsgToGroupNode",
"name": "Duplicate To Group Entities", "name": "Duplicate To Group Entities",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -169,7 +178,8 @@
}, },
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node", "name": "Device Profile Node",
"debugStrategy": "ALL_EVENTS", "debugFailures": false,
"debugAll": true,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -186,7 +196,8 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbJsFilterNode", "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",
"name": "Test JS script", "name": "Test JS script",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -204,7 +215,8 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbJsFilterNode", "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",
"name": "Test TBEL script", "name": "Test TBEL script",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -222,7 +234,8 @@
}, },
"type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode", "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode",
"name": "Add arrival timestamp", "name": "Add arrival timestamp",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -240,7 +253,8 @@
}, },
"type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode", "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode",
"name": "Calculate additional latencies", "name": "Calculate additional latencies",
"debugStrategy": "ALL_EVENTS", "debugFailures": false,
"debugAll": true,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -258,7 +272,8 @@
}, },
"type": "org.thingsboard.rule.engine.transform.TbChangeOriginatorNode", "type": "org.thingsboard.rule.engine.transform.TbChangeOriginatorNode",
"name": "To latencies asset", "name": "To latencies asset",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -287,7 +302,8 @@
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries", "name": "Save Timeseries",
"debugStrategy": "ALL_EVENTS", "debugFailures": false,
"debugAll": true,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -303,7 +319,8 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbCheckMessageNode", "type": "org.thingsboard.rule.engine.filter.TbCheckMessageNode",
"name": "Has testData", "name": "Has testData",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -323,7 +340,8 @@
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries with TTL", "name": "Save Timeseries with TTL",
"debugStrategy": "ALL_EVENTS", "debugFailures": false,
"debugAll": true,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {

9
msa/black-box-tests/src/test/resources/MqttRuleNodeTestMetadata.json

@ -9,7 +9,8 @@
}, },
"type": "org.thingsboard.rule.engine.mqtt.TbMqttNode", "type": "org.thingsboard.rule.engine.mqtt.TbMqttNode",
"name": "test mqtt", "name": "test mqtt",
"debugStrategy": "ALL_EVENTS", "debugFailures": false,
"debugAll": true,
"singletonMode": true, "singletonMode": true,
"queueName": "HighPriority", "queueName": "HighPriority",
"configurationVersion": 0, "configurationVersion": 0,
@ -36,7 +37,8 @@
}, },
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "save timeseries", "name": "save timeseries",
"debugStrategy": "ALL_EVENTS", "debugFailures": false,
"debugAll": true,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {
@ -54,7 +56,8 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "switch", "name": "switch",
"debugStrategy": "DISABLED", "debugFailures": false,
"debugAll": false,
"singletonMode": false, "singletonMode": false,
"configurationVersion": 0, "configurationVersion": 0,
"configuration": { "configuration": {

9
msa/black-box-tests/src/test/resources/RpcResponseRuleChainMetadata.json

@ -8,7 +8,8 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "msgTypeSwitch", "name": "msgTypeSwitch",
"debugStrategy": "ALL_EVENTS", "debugFailures": false,
"debugAll": true,
"configuration": { "configuration": {
"version": 0 "version": 0
} }
@ -20,7 +21,8 @@
}, },
"type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode", "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode",
"name": "formResponse", "name": "formResponse",
"debugStrategy": "ALL_EVENTS", "debugFailures": false,
"debugAll": true,
"configuration": { "configuration": {
"jsScript": "if (msg.method == \"getResponse\") {\n return {msg: {\"response\": \"requestReceived\"}, metadata: metadata, msgType: msgType};\n}\n\nreturn {msg: msg, metadata: metadata, msgType: msgType};" "jsScript": "if (msg.method == \"getResponse\") {\n return {msg: {\"response\": \"requestReceived\"}, metadata: metadata, msgType: msgType};\n}\n\nreturn {msg: msg, metadata: metadata, msgType: msgType};"
} }
@ -32,7 +34,8 @@
}, },
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCReplyNode", "type": "org.thingsboard.rule.engine.rpc.TbSendRPCReplyNode",
"name": "rpcReply", "name": "rpcReply",
"debugStrategy": "ALL_EVENTS", "debugFailures": false,
"debugAll": true,
"configuration": { "configuration": {
"requestIdMetaDataAttribute": "requestId" "requestIdMetaDataAttribute": "requestId"
} }

2
msa/js-executor/docker/Dockerfile

@ -14,7 +14,7 @@
# limitations under the License. # limitations under the License.
# #
FROM thingsboard/node:16.20.2-bookworm-slim FROM thingsboard/node:20.18.0-bookworm-slim
ENV NODE_ENV production ENV NODE_ENV production
ENV DOCKER_MODE true ENV DOCKER_MODE true

48
msa/js-executor/package.json

@ -6,26 +6,26 @@
"main": "server.ts", "main": "server.ts",
"bin": "server.js", "bin": "server.js",
"scripts": { "scripts": {
"pkg": "tsc && pkg -t node16-linux-x64,node16-win-x64 --out-path ./target ./target/src && node install.js", "pkg": "tsc && pkg -t node18-linux-x64,node18-win-x64 --out-path ./target ./target/src && node install.js",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon --watch '.' --ext 'ts' --exec 'ts-node server.ts'", "start": "nodemon --watch '.' --ext 'ts' --exec 'ts-node server.ts'",
"start-prod": "nodemon --watch '.' --ext 'ts' --exec 'NODE_ENV=production ts-node server.ts'", "start-prod": "nodemon --watch '.' --ext 'ts' --exec 'NODE_ENV=production ts-node server.ts'",
"build": "tsc" "build": "tsc"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-sqs": "^3.121.0", "@aws-sdk/client-sqs": "^3.682.0",
"@azure/service-bus": "^7.5.1", "@azure/service-bus": "^7.9.5",
"@google-cloud/pubsub": "^4.3.3", "@google-cloud/pubsub": "^4.8.0",
"amqplib": "^0.10.0", "amqplib": "^0.10.4",
"config": "^3.3.7", "config": "^3.3.12",
"express": "^4.18.1", "express": "^4.21.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"kafkajs": "^2.1.0", "kafkajs": "^2.2.4",
"long": "^5.2.0", "long": "^5.2.3",
"uuid-parse": "^1.1.0", "uuid-parse": "^1.1.0",
"uuid-random": "^1.3.2", "uuid-random": "^1.3.2",
"winston": "^3.7.2", "winston": "^3.16.0",
"winston-daily-rotate-file": "^4.7.1" "winston-daily-rotate-file": "^5.0.0"
}, },
"nyc": { "nyc": {
"exclude": [ "exclude": [
@ -36,26 +36,20 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@types/amqplib": "^0.8.2", "@types/amqplib": "^0.10.5",
"@types/config": "^0.0.41", "@types/config": "^3.3.5",
"@types/express": "^4.17.13", "@types/express": "~4.17.21",
"@types/node": "^18.0.1", "@types/node": "~20.17.6",
"@types/uuid-parse": "^1.0.0", "@types/uuid-parse": "^1.0.2",
"fs-extra": "^10.1.0", "fs-extra": "^11.2.0",
"nodemon": "^2.0.16", "nodemon": "^3.1.7",
"pkg": "^5.7.0", "pkg": "^5.8.1",
"ts-node": "^10.8.1", "ts-node": "^10.9.2",
"typescript": "^4.7.4" "typescript": "5.5.4"
}, },
"pkg": { "pkg": {
"assets": [ "assets": [
"node_modules/config/**/*.*" "node_modules/config/**/*.*"
] ]
},
"resolutions": {
"ansi-regex": "^5.0.1",
"color-string": "^1.5.5",
"minimist": "^1.2.6",
"node-fetch": "^2.6.7"
} }
} }

4
msa/js-executor/pom.xml

@ -71,8 +71,8 @@
<goal>install-node-and-yarn</goal> <goal>install-node-and-yarn</goal>
</goals> </goals>
<configuration> <configuration>
<nodeVersion>v16.20.2</nodeVersion> <nodeVersion>v20.18.0</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion> <yarnVersion>v1.22.22</yarnVersion>
</configuration> </configuration>
</execution> </execution>
<execution> <execution>

2
msa/js-executor/queue/awsSqsTemplate.ts

@ -52,7 +52,7 @@ export class AwsSqsTemplate implements IQueue {
private queueAttributes: { [n: string]: string } = { private queueAttributes: { [n: string]: string } = {
FifoQueue: 'true' FifoQueue: 'true'
}; };
private timer: NodeJS.Timer; private timer: NodeJS.Timeout;
name = 'AWS SQS'; name = 'AWS SQS';

2
msa/js-executor/queue/serviceBusTemplate.ts

@ -57,7 +57,7 @@ export class ServiceBusTemplate implements IQueue {
this.parseQueueProperties(); this.parseQueueProperties();
const listQueues = await this.serviceBusService.listQueues(); const listQueues = this.serviceBusService.listQueues();
for await (const queue of listQueues) { for await (const queue of listQueues) {
this.queues.push(queue.name); this.queues.push(queue.name);
} }

4310
msa/js-executor/yarn.lock

File diff suppressed because it is too large

4
msa/vc-executor/src/main/resources/tb-vc-executor.yml

@ -47,7 +47,9 @@ zk:
# Queue configuration parameters # Queue configuration parameters
queue: queue:
type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) # kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka). prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
in_memory: in_memory:
stats: stats:

2
msa/web-ui/docker/Dockerfile

@ -14,7 +14,7 @@
# limitations under the License. # limitations under the License.
# #
FROM thingsboard/node:16.20.2-bookworm-slim FROM thingsboard/node:20.18.0-bookworm-slim
ENV NODE_ENV production ENV NODE_ENV production
ENV DOCKER_MODE true ENV DOCKER_MODE true

39
msa/web-ui/package.json

@ -6,22 +6,22 @@
"main": "server.ts", "main": "server.ts",
"bin": "server.js", "bin": "server.js",
"scripts": { "scripts": {
"pkg": "tsc && pkg -t node16-linux-x64,node16-win-x64 --out-path ./target ./target/src && node install.js", "pkg": "tsc && pkg -t node18-linux-x64,node18-win-x64 --out-path ./target ./target/src && node install.js",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon --watch '.' --ext 'ts' --exec 'WEB_FOLDER=./target/web ts-node server.ts'", "start": "nodemon --watch '.' --ext 'ts' --exec 'WEB_FOLDER=./target/web ts-node server.ts'",
"start-prod": "nodemon --watch '.' --ext 'ts' --exec 'WEB_FOLDER=./target/web NODE_ENV=production ts-node server.ts'", "start-prod": "nodemon --watch '.' --ext 'ts' --exec 'WEB_FOLDER=./target/web NODE_ENV=production ts-node server.ts'",
"build": "tsc" "build": "tsc"
}, },
"dependencies": { "dependencies": {
"compression": "^1.7.4", "compression": "^1.7.5",
"config": "^3.3.7", "config": "^3.3.12",
"connect-history-api-fallback": "^1.6.0", "connect-history-api-fallback": "^1.6.0",
"express": "^4.18.1", "express": "^4.21.1",
"http": "0.0.0", "http": "0.0.0",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"winston": "^3.7.2", "winston": "^3.16.0",
"winston-daily-rotate-file": "^4.7.1" "winston-daily-rotate-file": "^5.0.0"
}, },
"nyc": { "nyc": {
"exclude": [ "exclude": [
@ -32,26 +32,21 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@types/compression": "^1.7.2", "@types/compression": "^1.7.5",
"@types/config": "^0.0.41", "@types/config": "^3.3.5",
"@types/connect-history-api-fallback": "^1.3.5", "@types/connect-history-api-fallback": "^1.5.4",
"@types/express": "^4.17.13", "@types/express": "~4.17.21",
"@types/http-proxy": "^1.17.9", "@types/http-proxy": "^1.17.15",
"@types/node": "^18.0.0", "@types/node": "~20.17.6",
"fs-extra": "^10.1.0", "fs-extra": "^11.2.0",
"nodemon": "^2.0.16", "nodemon": "^3.1.7",
"pkg": "^5.7.0", "pkg": "^5.8.1",
"ts-node": "^10.8.1", "ts-node": "^10.9.2",
"typescript": "^4.7.4" "typescript": "5.5.4"
}, },
"pkg": { "pkg": {
"assets": [ "assets": [
"node_modules/config/**/*.*" "node_modules/config/**/*.*"
] ]
},
"resolutions": {
"color-string": "^1.5.5",
"follow-redirects": "^1.14.8",
"minimist": "^1.2.6"
} }
} }

4
msa/web-ui/pom.xml

@ -80,8 +80,8 @@
<goal>install-node-and-yarn</goal> <goal>install-node-and-yarn</goal>
</goals> </goals>
<configuration> <configuration>
<nodeVersion>v16.20.2</nodeVersion> <nodeVersion>v20.18.0</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion> <yarnVersion>v1.22.22</yarnVersion>
</configuration> </configuration>
</execution> </execution>
<execution> <execution>

1982
msa/web-ui/yarn.lock

File diff suppressed because it is too large

37
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java

@ -52,7 +52,7 @@ import java.util.stream.Collectors;
@RuleNode(type = ComponentType.ENRICHMENT, @RuleNode(type = ComponentType.ENRICHMENT,
name = "originator telemetry", name = "originator telemetry",
configClazz = TbGetTelemetryNodeConfiguration.class, configClazz = TbGetTelemetryNodeConfiguration.class,
version = 1, version = 2,
nodeDescription = "Adds message originator telemetry for selected time range into message metadata", nodeDescription = "Adds message originator telemetry for selected time range into message metadata",
nodeDetails = "Useful when you need to get telemetry data set from the message originator for a specific time range " + nodeDetails = "Useful when you need to get telemetry data set from the message originator for a specific time range " +
"instead of fetching just the latest telemetry or if you need to get the closest telemetry to the fetch interval start or end. " + "instead of fetching just the latest telemetry or if you need to get the closest telemetry to the fetch interval start or end. " +
@ -232,21 +232,21 @@ public class TbGetTelemetryNode implements TbNode {
public TbPair<Boolean, JsonNode> upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { public TbPair<Boolean, JsonNode> upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException {
boolean hasChanges = false; boolean hasChanges = false;
switch (fromVersion) { switch (fromVersion) {
case 0 -> { case 0: {
if (oldConfiguration.hasNonNull("fetchMode")) { if (oldConfiguration.hasNonNull("fetchMode")) {
String fetchMode = oldConfiguration.get("fetchMode").asText(); String fetchMode = oldConfiguration.get("fetchMode").asText();
switch (fetchMode) { switch (fetchMode) {
case "FIRST": case "FIRST" -> {
((ObjectNode) oldConfiguration).put("orderBy", Direction.ASC.name()); ((ObjectNode) oldConfiguration).put("orderBy", Direction.ASC.name());
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name()); ((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true; hasChanges = true;
break; }
case "LAST": case "LAST" -> {
((ObjectNode) oldConfiguration).put("orderBy", Direction.DESC.name()); ((ObjectNode) oldConfiguration).put("orderBy", Direction.DESC.name());
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name()); ((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true; hasChanges = true;
break; }
case "ALL": case "ALL" -> {
if (oldConfiguration.has("orderBy") && if (oldConfiguration.has("orderBy") &&
(oldConfiguration.get("orderBy").isNull() || oldConfiguration.get("orderBy").asText().isEmpty())) { (oldConfiguration.get("orderBy").isNull() || oldConfiguration.get("orderBy").asText().isEmpty())) {
((ObjectNode) oldConfiguration).put("orderBy", Direction.ASC.name()); ((ObjectNode) oldConfiguration).put("orderBy", Direction.ASC.name());
@ -257,16 +257,33 @@ public class TbGetTelemetryNode implements TbNode {
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name()); ((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true; hasChanges = true;
} }
break; }
default: default -> {
((ObjectNode) oldConfiguration).put("fetchMode", FetchMode.LAST.name()); ((ObjectNode) oldConfiguration).put("fetchMode", FetchMode.LAST.name());
((ObjectNode) oldConfiguration).put("orderBy", Direction.DESC.name()); ((ObjectNode) oldConfiguration).put("orderBy", Direction.DESC.name());
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name()); ((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true; hasChanges = true;
break; }
} }
} }
} }
case 1: {
if (!oldConfiguration.hasNonNull("limit")) {
((ObjectNode) oldConfiguration).put("limit", 1000);
hasChanges = true;
}
if (oldConfiguration.has("fetchMode") && oldConfiguration.get("fetchMode").asText().equals("ALL")) {
if (!oldConfiguration.hasNonNull("aggregation")) {
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true;
}
if (!oldConfiguration.hasNonNull("orderBy")) {
((ObjectNode) oldConfiguration).put("orderBy", Direction.ASC.name());
hasChanges = true;
}
}
break;
}
} }
return new TbPair<>(hasChanges, oldConfiguration); return new TbPair<>(hasChanges, oldConfiguration);
} }

32
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java

@ -637,6 +637,38 @@ public class TbGetTelemetryNodeTest extends AbstractRuleNodeUpgradeTest {
"endInterval": 1, "endInterval": 1,
"endIntervalTimeUnit": "MINUTES" "endIntervalTimeUnit": "MINUTES"
} }
"""),
// config for version 0 (fetchMode is 'ALL' and limit, aggregation and orderBy do not exist)
Arguments.of(0,
"""
{
"latestTsKeyNames": ["key"],
"fetchMode": "ALL",
"useMetadataIntervalPatterns": false,
"startIntervalPattern": "",
"endIntervalPattern": "",
"startInterval": 2,
"startIntervalTimeUnit": "MINUTES",
"endInterval": 1,
"endIntervalTimeUnit": "MINUTES"
}
""",
true,
"""
{
"latestTsKeyNames": ["key"],
"aggregation": "NONE",
"fetchMode": "ALL",
"orderBy": "ASC",
"limit": 1000,
"useMetadataIntervalPatterns": false,
"startIntervalPattern": "",
"endIntervalPattern": "",
"startInterval": 2,
"startIntervalTimeUnit": "MINUTES",
"endInterval": 1,
"endIntervalTimeUnit": "MINUTES"
}
""") """)
); );
} }

4
transport/coap/src/main/resources/tb-coap-transport.yml

@ -232,7 +232,9 @@ coap:
# Queue configuration parameters # Queue configuration parameters
queue: queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) # kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka). prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka: kafka:
# Kafka Bootstrap Servers # Kafka Bootstrap Servers

4
transport/http/src/main/resources/tb-http-transport.yml

@ -202,7 +202,9 @@ transport:
# Queue configuration parameters # Queue configuration parameters
queue: queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) # kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka) . prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka) .
kafka: kafka:
# Kafka Bootstrap Servers # Kafka Bootstrap Servers

4
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -302,7 +302,9 @@ transport:
# Queue configuration properties # Queue configuration properties
queue: queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) # kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka). prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka: kafka:
# Kafka Bootstrap Servers # Kafka Bootstrap Servers

4
transport/mqtt/src/main/resources/tb-mqtt-transport.yml

@ -235,7 +235,9 @@ transport:
# Queue configuration parameters # Queue configuration parameters
queue: queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) # kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka). prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka: kafka:
# Kafka Bootstrap Servers # Kafka Bootstrap Servers

4
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -181,7 +181,9 @@ transport:
# Queue configuration parameters # Queue configuration parameters
queue: queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) # kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka). prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka: kafka:
# Kafka Bootstrap Servers # Kafka Bootstrap Servers

2
ui-ngx/pom.xml

@ -56,7 +56,7 @@
<goal>install-node-and-yarn</goal> <goal>install-node-and-yarn</goal>
</goals> </goals>
<configuration> <configuration>
<nodeVersion>v20.11.1</nodeVersion> <nodeVersion>v20.18.0</nodeVersion>
<yarnVersion>v1.22.22</yarnVersion> <yarnVersion>v1.22.22</yarnVersion>
</configuration> </configuration>
</execution> </execution>

18
ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts

@ -22,11 +22,11 @@ import { MatDialog } from '@angular/material/dialog';
import { import {
EntityConflictDialogComponent EntityConflictDialogComponent
} from '@shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component'; } from '@shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component';
import { HasId } from '@shared/models/base-data'; import { EntityInfoData, VersionedEntity } from '@shared/models/entity.models';
import { HasVersion } from '@shared/models/entity.models';
import { getInterceptorConfig } from './interceptor.util'; import { getInterceptorConfig } from './interceptor.util';
import { isDefined } from '@core/utils'; import { isDefined } from '@core/utils';
import { InterceptorConfig } from '@core/interceptors/interceptor-config'; import { InterceptorConfig } from '@core/interceptors/interceptor-config';
import { RuleChainMetaData } from '@shared/models/rule-chain.models';
@Injectable() @Injectable()
export class EntityConflictInterceptor implements HttpInterceptor { export class EntityConflictInterceptor implements HttpInterceptor {
@ -35,7 +35,7 @@ export class EntityConflictInterceptor implements HttpInterceptor {
private dialog: MatDialog, private dialog: MatDialog,
) {} ) {}
intercept(request: HttpRequest<unknown & HasId & HasVersion>, next: HttpHandler): Observable<HttpEvent<unknown>> { intercept(request: HttpRequest<VersionedEntity>, next: HttpHandler): Observable<HttpEvent<unknown>> {
if (!request.url.startsWith('/api/')) { if (!request.url.startsWith('/api/')) {
return next.handle(request); return next.handle(request);
} }
@ -52,11 +52,11 @@ export class EntityConflictInterceptor implements HttpInterceptor {
} }
private handleConflictError( private handleConflictError(
request: HttpRequest<unknown & HasId & HasVersion>, request: HttpRequest<VersionedEntity>,
next: HttpHandler, next: HttpHandler,
error: HttpErrorResponse error: HttpErrorResponse
): Observable<HttpEvent<unknown>> { ): Observable<HttpEvent<unknown>> {
if (getInterceptorConfig(request).ignoreVersionConflict) { if (getInterceptorConfig(request).ignoreVersionConflict || !this.isVersionedEntity(request.body)) {
return throwError(() => error); return throwError(() => error);
} }
@ -74,12 +74,16 @@ export class EntityConflictInterceptor implements HttpInterceptor {
); );
} }
private updateRequestVersion(request: HttpRequest<unknown & HasId & HasVersion>): HttpRequest<unknown & HasId & HasVersion> { private updateRequestVersion(request: HttpRequest<VersionedEntity>): HttpRequest<VersionedEntity> {
const body = { ...request.body, version: null }; const body = { ...request.body, version: null };
return request.clone({ body }); return request.clone({ body });
} }
private openConflictDialog(entity: unknown & HasId & HasVersion, message: string): Observable<boolean> { private isVersionedEntity(entity: VersionedEntity): boolean {
return !!((entity as EntityInfoData)?.id ?? (entity as RuleChainMetaData)?.ruleChainId)
}
private openConflictDialog(entity: VersionedEntity, message: string): Observable<boolean> {
const dialogRef = this.dialog.open(EntityConflictDialogComponent, { const dialogRef = this.dialog.open(EntityConflictDialogComponent, {
disableClose: true, disableClose: true,
data: { message, entity }, data: { message, entity },

2
ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html

@ -73,7 +73,7 @@
</mat-chip-listbox> </mat-chip-listbox>
</div> </div>
<div class="tb-form-row column-xs"> <div class="tb-form-row column-xs">
<div class="fixed-title-width" ranslate>alarm.alarm-type-list</div> <div class="fixed-title-width" translate>alarm.alarm-type-list</div>
<tb-entity-subtype-list subscriptSizing="dynamic" <tb-entity-subtype-list subscriptSizing="dynamic"
formControlName="typeList" formControlName="typeList"
appearance="outline" appearance="outline"

1
ui-ngx/src/app/modules/home/components/dashboard-page/layout/select-dashboard-breakpoint.component.scss

@ -26,6 +26,7 @@
.mat-mdc-select.select-dashboard-breakpoint { .mat-mdc-select.select-dashboard-breakpoint {
.mat-mdc-select-value { .mat-mdc-select-value {
max-width: 200px; max-width: 200px;
font-size: 14px;
} }
.mat-mdc-select-arrow { .mat-mdc-select-arrow {
width: 24px; width: 24px;

1
ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.scss

@ -22,6 +22,7 @@
.mat-mdc-select.default-state-controller { .mat-mdc-select.default-state-controller {
.mat-mdc-select-value { .mat-mdc-select-value {
max-width: 200px; max-width: 200px;
font-size: 14px;
} }
.mat-mdc-select-arrow { .mat-mdc-select-arrow {
width: 24px; width: 24px;

3
ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts

@ -187,7 +187,8 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
} }
public getStateName(id: string, state: DashboardState): string { public getStateName(id: string, state: DashboardState): string {
return this.utils.customTranslation(state.name, id); const name = this.utils.customTranslation(state.name, id);
return name === this.stateControllerId() ? name.charAt(0).toUpperCase() + name.slice(1) : name;
} }
public getCurrentStateName(): string { public getCurrentStateName(): string {

2
ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html

@ -43,7 +43,7 @@
</div> </div>
<div class="tb-hint">{{ hintText | translate }}</div> <div class="tb-hint">{{ hintText | translate }}</div>
</div> </div>
<div class="flex flex-col" [class.!hidden]="!dynamicMode && !onlyUserDynamicSource"> <div class="flex flex-col flex-1" [class.!hidden]="!dynamicMode && !onlyUserDynamicSource">
<div formGroupName="dynamicValue" class="flex flex-row items-center justify-start gap-2"> <div formGroupName="dynamicValue" class="flex flex-row items-center justify-start gap-2">
<div class="flex max-w-35% flex-full flex-col"> <div class="flex max-w-35% flex-full flex-col">
<mat-form-field hideRequiredMarker class="mat-block"> <mat-form-field hideRequiredMarker class="mat-block">

3
ui-ngx/src/app/modules/home/components/widget/widget-container.component.html

@ -38,6 +38,7 @@
[class]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel && !widgetComponent.widgetContext?.embedTitlePanel && (widget.showTitle||widget.hasAggregation))}" [class]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel && !widgetComponent.widgetContext?.embedTitlePanel && (widget.showTitle||widget.hasAggregation))}"
(mousedown)="$event.stopPropagation()"> (mousedown)="$event.stopPropagation()">
<button mat-icon-button *ngFor="let action of widget.customHeaderActions" <button mat-icon-button *ngFor="let action of widget.customHeaderActions"
type="button"
[class.!hidden]="isEdit" [class.!hidden]="isEdit"
(click)="action.onAction($event)" (click)="action.onAction($event)"
matTooltip="{{ action.displayName }}" matTooltip="{{ action.displayName }}"
@ -45,6 +46,7 @@
<tb-icon>{{ action.icon }}</tb-icon> <tb-icon>{{ action.icon }}</tb-icon>
</button> </button>
<button mat-icon-button *ngFor="let action of widget.widgetActions" <button mat-icon-button *ngFor="let action of widget.widgetActions"
type="button"
[class.!hidden]="isEdit || !action.show" [class.!hidden]="isEdit || !action.show"
(click)="action.onAction($event)" (click)="action.onAction($event)"
matTooltip="{{ action.name | translate }}" matTooltip="{{ action.name | translate }}"
@ -52,6 +54,7 @@
<tb-icon>{{ action.icon }}</tb-icon> <tb-icon>{{ action.icon }}</tb-icon>
</button> </button>
<button mat-icon-button <button mat-icon-button
type="button"
[class.!hidden]="isEdit || !widget.enableFullscreen" [class.!hidden]="isEdit || !widget.enableFullscreen"
(click)="$event.stopPropagation(); widget.isFullscreen = !widget.isFullscreen; updateEditWidgetActionsTooltipState()" (click)="$event.stopPropagation(); widget.isFullscreen = !widget.isFullscreen; updateEditWidgetActionsTooltipState()"
matTooltip="{{(widget.isFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" matTooltip="{{(widget.isFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"

8
ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts

@ -213,8 +213,8 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
}); });
} }
onClicked(event: MouseEvent) { onClicked(event: MouseEvent): void {
if (event) { if (event && this.isEdit) {
event.stopPropagation(); event.stopPropagation();
} }
this.widgetComponentAction.emit({ this.widgetComponentAction.emit({
@ -223,8 +223,8 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
}); });
} }
onContextMenu(event: TbContextMenuEvent) { onContextMenu(event: TbContextMenuEvent): void {
if (event) { if (event && this.isEdit) {
event.stopPropagation(); event.stopPropagation();
} }
this.widgetComponentAction.emit({ this.widgetComponentAction.emit({

6
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts

@ -214,7 +214,11 @@ export class ClientComponent extends EntityComponent<OAuth2Client, PageLink, OAu
const mapperConfig = control.get('mapperConfig') as UntypedFormGroup; const mapperConfig = control.get('mapperConfig') as UntypedFormGroup;
if (type === MapperType.CUSTOM) { if (type === MapperType.CUSTOM) {
mapperConfig.removeControl('basic'); mapperConfig.removeControl('basic');
mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); if (!mapperConfig.get('custom')) {
mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom));
} else {
mapperConfig.get('custom').patchValue(predefinedValue.custom, {emitEvent: false});
}
} else { } else {
mapperConfig.removeControl('custom'); mapperConfig.removeControl('custom');
if (!mapperConfig.get('basic')) { if (!mapperConfig.get('basic')) {

2
ui-ngx/src/app/modules/home/pages/customer/customer.component.html

@ -49,7 +49,7 @@
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'manageEdges')" (click)="onEntityAction($event, 'manageEdges')"
[class.!hidden]="isEdit"> [class.!hidden]="isEdit"
*ngIf="edgesSupportEnabled()"> *ngIf="edgesSupportEnabled()">
{{'customer.manage-edges' | translate }} {{'customer.manage-edges' | translate }}
</button> </button>

3
ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts

@ -127,7 +127,8 @@ export class RuleChainsTableConfigResolver {
const columns: Array<EntityColumn<RuleChain>> = []; const columns: Array<EntityColumn<RuleChain>> = [];
columns.push( columns.push(
new DateEntityTableColumn<RuleChain>('createdTime', 'common.created-time', this.datePipe, '150px'), new DateEntityTableColumn<RuleChain>('createdTime', 'common.created-time', this.datePipe, '150px'),
new EntityTableColumn<RuleChain>('name', 'rulechain.name', '100%') new EntityTableColumn<RuleChain>('name', 'rulechain.name', '50%'),
new EntityTableColumn<RuleChain>('description', 'rulechain.description', '50%', entity => entity.additionalInfo?.description ?? '')
); );
if (ruleChainScope === 'tenant' || ruleChainScope === 'edge') { if (ruleChainScope === 'tenant' || ruleChainScope === 'edge') {
columns.push( columns.push(

1
ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.html

@ -19,6 +19,7 @@
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2 translate>widget.select-widget-type</h2> <h2 translate>widget.select-widget-type</h2>
<span class="flex-1"></span> <span class="flex-1"></span>
<div tb-help="widgetTypes"></div>
<button mat-icon-button <button mat-icon-button
(click)="cancel()" (click)="cancel()"
type="button"> type="button">

2
ui-ngx/src/app/shared/components/country-autocomplete.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<mat-form-field [formGroup]="countryFormGroup" class="mat-block" subscriptSizing="{{subscriptSizing}}"> <mat-form-field [formGroup]="countryFormGroup" class="mat-block" subscriptSizing="{{subscriptSizing}}" [appearance]="appearance">
<mat-label>{{ labelText }}</mat-label> <mat-label>{{ labelText }}</mat-label>
<input matInput type="text" <input matInput type="text"
#countryInput #countryInput

5
ui-ngx/src/app/shared/components/country-autocomplete.component.ts

@ -28,7 +28,7 @@ import {
import { isNotEmptyStr } from '@core/utils'; import { isNotEmptyStr } from '@core/utils';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators'; import { debounceTime, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators';
import { SubscriptSizing } from '@angular/material/form-field'; import { SubscriptSizing, MatFormFieldAppearance } from '@angular/material/form-field';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -71,6 +71,9 @@ export class CountryAutocompleteComponent implements OnInit, ControlValueAccesso
@coerceBoolean() @coerceBoolean()
required = false; required = false;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Input() @Input()
subscriptSizing: SubscriptSizing = 'fixed'; subscriptSizing: SubscriptSizing = 'fixed';

4
ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts

@ -20,13 +20,13 @@ import { SharedModule } from '@shared/shared.module';
import { ImportExportService } from '@shared/import-export/import-export.service'; import { ImportExportService } from '@shared/import-export/import-export.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { entityTypeTranslations } from '@shared/models/entity-type.models'; import { entityTypeTranslations } from '@shared/models/entity-type.models';
import { EntityInfoData } from '@shared/models/entity.models'; import { EntityInfoData, VersionedEntity } from '@shared/models/entity.models';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { RuleChainMetaData } from '@shared/models/rule-chain.models'; import { RuleChainMetaData } from '@shared/models/rule-chain.models';
interface EntityConflictDialogData { interface EntityConflictDialogData {
message: string; message: string;
entity: EntityInfoData | RuleChainMetaData; entity: VersionedEntity;
} }
@Component({ @Component({

9
ui-ngx/src/app/shared/import-export/import-export.service.ts

@ -55,7 +55,12 @@ import { EntityType } from '@shared/models/entity-type.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { WidgetService } from '@core/http/widget.service'; import { WidgetService } from '@core/http/widget.service';
import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { EntityInfoData, ImportEntitiesResultInfo, ImportEntityData } from '@shared/models/entity.models'; import {
EntityInfoData,
ImportEntitiesResultInfo,
ImportEntityData,
VersionedEntity
} from '@shared/models/entity.models';
import { RequestConfig } from '@core/http/http-utils'; import { RequestConfig } from '@core/http/http-utils';
import { RuleChain, RuleChainImport, RuleChainMetaData, RuleChainType } from '@shared/models/rule-chain.models'; import { RuleChain, RuleChainImport, RuleChainMetaData, RuleChainType } from '@shared/models/rule-chain.models';
import { RuleChainService } from '@core/http/rule-chain.service'; import { RuleChainService } from '@core/http/rule-chain.service';
@ -361,7 +366,7 @@ export class ImportExportService {
}); });
} }
public exportEntity(entityData: EntityInfoData | RuleChainMetaData): void { public exportEntity(entityData: VersionedEntity): void {
const id = (entityData as EntityInfoData).id ?? (entityData as RuleChainMetaData).ruleChainId; const id = (entityData as EntityInfoData).id ?? (entityData as RuleChainMetaData).ruleChainId;
let fileName = (entityData as EntityInfoData).name; let fileName = (entityData as EntityInfoData).name;
let preparedData; let preparedData;

65
ui-ngx/src/app/shared/models/ace/service-completion.models.ts

@ -102,6 +102,10 @@ export const customDialogComponentHref = '<a href="https://github.com/thingsboar
export const resourceInfoHref = '<a href="https://github.com/thingsboard/thingsboard/blob/b033b51712244d08e0f5e0beb8be60c9f8fa4cd2/ui-ngx/src/app/shared/models/resource.models.ts#L51" target="_blank">Resource info</a>'; export const resourceInfoHref = '<a href="https://github.com/thingsboard/thingsboard/blob/b033b51712244d08e0f5e0beb8be60c9f8fa4cd2/ui-ngx/src/app/shared/models/resource.models.ts#L51" target="_blank">Resource info</a>';
export const bulkImportResultHref = '<a href="https://github.com/thingsboard/thingsboard/blob/1abaa6f1188e5adc80912e7475ccb6347a822c8d/ui-ngx/src/app/shared/import-export/import-export.models.ts#L135" target="_blank">Bulk import result</a>';
export const bulkImportRequestHref = '<a href="https://github.com/thingsboard/thingsboard/blob/1abaa6f1188e5adc80912e7475ccb6347a822c8d/ui-ngx/src/app/shared/import-export/import-export.models.ts#L125" target="_blank">Bulk import request</a>';
export const pageLinkArg: FunctionArg = { export const pageLinkArg: FunctionArg = {
name: 'pageLink', name: 'pageLink',
type: '<a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/page/page-link.ts#L68" target="_blank">PageLink</a>', type: '<a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/page/page-link.ts#L68" target="_blank">PageLink</a>',
@ -387,6 +391,67 @@ export const serviceCompletions: TbEditorCompletions = {
], ],
return: observablePageDataReturnType(assetInfoHref) return: observablePageDataReturnType(assetInfoHref)
}, },
getTenantAssetInfosByAssetProfileId: {
description: 'Get tenant asset infos by asset profile ID',
meta: 'function',
args: [
pageLinkArg,
{ name: 'assetProfileId', type: 'string', optional: true, description: 'ID of the asset profile' },
requestConfigArg
],
return: observablePageDataReturnType(assetInfoHref)
},
getCustomerAssetInfosByAssetProfileId: {
description: 'Get customer asset infos by asset profile ID',
meta: 'function',
args: [
{ name: 'customerId', type: 'string', description: 'ID of the customer' },
pageLinkArg,
{ name: 'assetProfileId', type: 'string', optional: true, description: 'ID of the asset profile' },
requestConfigArg
],
return: observablePageDataReturnType(assetInfoHref)
},
assignAssetToEdge: {
description: 'Assign an asset to an edge',
meta: 'function',
args: [
{ name: 'edgeId', type: 'string', description: 'ID of the edge' },
{ name: 'assetId', type: 'string', description: 'ID of the asset' },
requestConfigArg
],
return: observableReturnType(assetHref)
},
unassignAssetFromEdge: {
description: 'Unassign an asset from an edge',
meta: 'function',
args: [
{ name: 'edgeId', type: 'string', description: 'ID of the edge' },
{ name: 'assetId', type: 'string', description: 'ID of the asset' },
requestConfigArg
],
return: observableVoid()
},
getEdgeAssets: {
description: 'Get assets assigned to an edge',
meta: 'function',
args: [
{ name: 'edgeId', type: 'string', description: 'ID of the edge' },
pageLinkArg,
{ name: 'type', type: 'string', optional: true, description: 'Asset type' },
requestConfigArg
],
return: observablePageDataReturnType(assetInfoHref)
},
bulkImportAssets: {
description: 'Bulk import assets with provided entities data',
meta: 'function',
args: [
{ name: 'entitiesData', type: bulkImportRequestHref, description: 'Data for bulk importing assets' },
requestConfigArg
],
return: observableReturnType(bulkImportResultHref)
},
getAsset: { getAsset: {
description: 'Get asset by id', description: 'Get asset by id',
meta: 'function', meta: 'function',

3
ui-ngx/src/app/shared/models/entity.models.ts

@ -20,6 +20,7 @@ import { EntityId } from '@shared/models/id/entity-id';
import { DeviceCredentialMQTTBasic } from '@shared/models/device.models'; import { DeviceCredentialMQTTBasic } from '@shared/models/device.models';
import { Lwm2mSecurityConfigModels } from '@shared/models/lwm2m-security-config.models'; import { Lwm2mSecurityConfigModels } from '@shared/models/lwm2m-security-config.models';
import { TenantId } from '@shared/models/id/tenant-id'; import { TenantId } from '@shared/models/id/tenant-id';
import { RuleChainMetaData } from '@shared/models/rule-chain.models';
export interface EntityInfo { export interface EntityInfo {
name?: string; name?: string;
@ -197,3 +198,5 @@ export interface HasDebugConfig {
debugFailures?: boolean; debugFailures?: boolean;
debugAllUntil?: number; debugAllUntil?: number;
} }
export type VersionedEntity = EntityInfoData & HasVersion | RuleChainMetaData;

4
ui-ngx/src/styles.scss

@ -74,7 +74,7 @@ body {
line-height: normal; line-height: normal;
} }
a:not(.mat-mdc-button-base):not(.mdc-tab) { a:not(.mat-mdc-button-base, .mdc-tab) {
font-weight: 400; font-weight: 400;
color: #106cc8; color: #106cc8;
text-decoration: none; text-decoration: none;
@ -85,7 +85,7 @@ a:not(.mat-mdc-button-base):not(.mdc-tab) {
a:hover, a:hover,
a:focus { a:focus {
&:not(.mat-mdc-button-base):not(.mdc-tab) { &:not(.mat-mdc-button-base, .mdc-tab) {
border-bottom: 1px solid #4054b2; border-bottom: 1px solid #4054b2;
} }
} }

Loading…
Cancel
Save