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",
"name": "Device Profile Node",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false
@ -34,7 +35,8 @@
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"defaultTTL": 0
},
@ -47,7 +49,8 @@
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configurationVersion": 2,
"configuration": {
"scope": "CLIENT_SCOPE",
@ -64,7 +67,8 @@
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"version": 0
},
@ -77,7 +81,8 @@
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"scriptLang": "TBEL",
"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",
"name": "Log Other",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"scriptLang": "TBEL",
"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",
"name": "RPC Call Request",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"timeoutInSeconds": 60
},
@ -120,7 +127,8 @@
},
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"scope": "SERVER_SCOPE"
},
@ -133,7 +141,8 @@
},
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"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",
"name": "Save Timeseries",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"defaultTTL": 0
}
@ -31,7 +32,8 @@
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configurationVersion": 2,
"configuration": {
"scope": "CLIENT_SCOPE",
@ -47,7 +49,8 @@
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"version": 0
}
@ -59,7 +62,8 @@
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"scriptLang": "TBEL",
"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",
"name": "Log Other",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"scriptLang": "TBEL",
"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",
"name": "RPC Call Request",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"timeoutInSeconds": 60
}
@ -100,7 +106,8 @@
},
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"configuration": {
"persistAlarmRulesState": 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",
"name": "Save Timeseries",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": {
"defaultTTL": 0
}
@ -30,7 +32,9 @@
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configurationVersion": 2,
"configuration": {
"scope": "CLIENT_SCOPE",
@ -46,7 +50,9 @@
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": {
"version": 0
}
@ -58,7 +64,9 @@
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": {
"scriptLang": "TBEL",
"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",
"name": "Log Other",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": {
"scriptLang": "TBEL",
"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",
"name": "RPC Call Request",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": {
"timeoutInSeconds": 60
}
@ -99,7 +111,9 @@
},
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node",
"debugStrategy": "DISABLED",
"debugFailures": false,
"debugAll": false,
"debugAllUntil": 0,
"configuration": {
"persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false
@ -140,4 +154,4 @@
],
"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
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
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
$$
BEGIN
@ -38,7 +38,7 @@ $$
FROM information_schema.columns
WHERE table_name = 'rule_node' AND column_name = 'debug_mode') THEN
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
DROP COLUMN debug_mode;
END IF;

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

@ -577,9 +577,9 @@ public class ActorSystemContext {
@Getter
private boolean externalNodeForceAck;
@Value("${actors.rule.node.max_debug_mode_duration:60}")
@Value("${debug_mode.max_duration:60}")
@Getter
private int maxRuleNodeDebugModeDurationMinutes;
private int maxDebugModeDurationMinutes;
@Value("${state.rule.node.deviceState.rateLimit:1:1,30:60,60:3600}")
@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.page.PageData;
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.common.util.DebugModeUtil;
import org.thingsboard.server.common.data.rule.RuleNodeState;
import org.thingsboard.server.common.data.script.ScriptLanguage;
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) {
RuleNode ruleNode = nodeCtx.getSelf();
DebugStrategy debugStrategy = ruleNode.getDebugStrategy();
if (debugStrategy.shouldPersistDebugOutputForAllEvents(ruleNode.getLastUpdateTs(), msg.getTs(), getMaxRuleNodeDebugDurationMinutes())) {
if (DebugModeUtil.isDebugAllAvailable(ruleNode)) {
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);
}
}
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.RuleChainType;
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.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.plugin.RuleNodeUpdatedMsg;
@ -255,13 +256,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
var originatorNodeId = envelope.getTargetRuleNodeId();
RuleNodeCtx ruleNodeCtx = nodeActors.get(originatorNodeId);
if (ruleNodeCtx != null) {
var ruleNode = ruleNodeCtx.getSelf();
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) {
if (DebugModeUtil.isDebugAvailable(ruleNodeCtx.getSelf(), 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.plugin.ComponentLifecycleState;
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.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.RuleNodeException;
@ -130,7 +131,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
if (maxRuleNodeExecutionsPerMessage == 0 || ruleNodeCount < maxRuleNodeExecutionsPerMessage) {
apiUsageClient.report(tenantId, tbMsg.getCustomerId(), ApiUsageRecordKey.RE_EXEC_COUNT);
persistDebugInputIfAllowed(msg.getMsg(), "Self", tenantProfileConfiguration
.getMaxRuleNodeDebugModeDurationMinutes(systemContext.getMaxRuleNodeDebugModeDurationMinutes()));
.getMaxDebugModeDurationMinutes(systemContext.getMaxDebugModeDurationMinutes()));
try {
tbNode.onMsg(defaultCtx, msg.getMsg());
} catch (Exception e) {
@ -154,7 +155,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
if (maxRuleNodeExecutionsPerMessage == 0 || ruleNodeCount < maxRuleNodeExecutionsPerMessage) {
apiUsageClient.report(tenantId, tbMsg.getCustomerId(), ApiUsageRecordKey.RE_EXEC_COUNT);
persistDebugInputIfAllowed(msg.getMsg(), msg.getFromRelationType(), tenantProfileConfiguration
.getMaxRuleNodeDebugModeDurationMinutes(systemContext.getMaxRuleNodeDebugModeDurationMinutes()));
.getMaxDebugModeDurationMinutes(systemContext.getMaxDebugModeDurationMinutes()));
try {
tbNode.onMsg(msg.getCtx(), msg.getMsg());
} catch (Exception e) {
@ -218,7 +219,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
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);
}
}

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}")
private long maxDatapointsLimit;
@Value("${actors.rule.node.max_debug_mode_duration:60}")
private int maxRuleNodeDebugModeDurationMinutes;
@Value("${debug_mode.max_duration:60}")
private int maxDebugModeDurationMinutes;
@Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled:true}")
@Getter
@ -153,7 +153,7 @@ public class SystemInfoController extends BaseController {
if (!currentUser.isSystemAdmin()) {
DefaultTenantProfileConfiguration tenantProfileConfiguration = tenantProfileCache.get(tenantId).getDefaultProfileConfiguration();
systemParams.setMaxResourceSize(tenantProfileConfiguration.getMaxResourceSize());
systemParams.setMaxRuleNodeDebugDurationMinutes(tenantProfileConfiguration.getMaxRuleNodeDebugModeDurationMinutes(maxRuleNodeDebugModeDurationMinutes));
systemParams.setMaxDebugModeDurationMinutes(tenantProfileConfiguration.getMaxDebugModeDurationMinutes(maxDebugModeDurationMinutes));
if (ruleChainDebugPerTenantLimitsEnabled) {
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" +
" \"maxDPStorageDays\": 0,\n" +
" \"maxRuleNodeExecutionsPerMessage\": 50,\n" +
" \"maxRuleNodeDebugDurationMinutes\": 15,\n" +
" \"maxDebugModeDurationMinutes\": 15,\n" +
" \"maxEmails\": 0,\n" +
" \"maxSms\": 0,\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.RuleChainMetaData;
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.NodeConnectionInfoProto;
import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto;
@ -89,8 +88,8 @@ public abstract class BaseRuleChainMetadataConstructor implements RuleChainMetad
.setIdLSB(node.getId().getId().getLeastSignificantBits())
.setType(node.getType())
.setName(node.getName())
.setLastUpdateTs(node.getLastUpdateTs())
.setDebugStrategy(DebugStrategy.forNumber(node.getDebugStrategy().getProtoNumber()))
.setDebugFailures(node.isDebugFailures())
.setDebugAllUntil(node.getDebugAllUntil())
.setConfiguration(JacksonUtil.toString(node.getConfiguration()))
.setAdditionalInfo(JacksonUtil.toString(node.getAdditionalInfo()))
.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.setRuleEngineExceptionsTtlDays(0);
configuration.setMaxRuleNodeExecutionsPerMessage(0);
configuration.setMaxRuleNodeDebugDurationMinutes(0);
configuration.setMaxDebugModeDurationMinutes(0);
tenantProfileData.setConfiguration(configuration);
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);
ruleChainMetaData.setRuleChainId(ruleChain.getId());
ruleChainService.saveRuleChainMetaData(TenantId.SYS_TENANT_ID, ruleChainMetaData, Function.identity(), false);
ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData, Function.identity(), false);
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(),
DefaultNotifications.queueTypeDeprecation.toTemplate(), new GeneralNotificationInfo(Map.of(
"queueType", queueTypeName

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

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

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.server.common.data.edge.Edge;
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.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
@ -229,7 +228,7 @@ public class RuleChainEdgeTest extends AbstractEdgeTest {
// update metadata for root rule chain
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);
Assert.assertTrue(edgeImitator.waitForMessages());
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.TbNodeConnectionType;
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.RuleChain;
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.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration1.setFetchTo(TbMsgSource.METADATA);
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
@ -153,7 +152,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration2.setFetchTo(TbMsgSource.METADATA);
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
@ -249,7 +248,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration1.setFetchTo(TbMsgSource.METADATA);
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
@ -258,7 +257,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
RuleNode ruleNode12 = new RuleNode();
ruleNode12.setName("Simple Rule Node 1");
ruleNode12.setType(org.thingsboard.rule.engine.flow.TbRuleChainInputNode.class.getName());
ruleNode12.setDebugStrategy(DebugStrategy.ALL_EVENTS);
ruleNode12.setDebugAll(true);
TbRuleChainInputNodeConfiguration configuration12 = new TbRuleChainInputNodeConfiguration();
configuration12.setRuleChainId(secondaryRuleChain.getId().getId().toString());
ruleNode12.setConfiguration(JacksonUtil.valueToTree(configuration12));
@ -283,7 +282,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration2.setFetchTo(TbMsgSource.METADATA);
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.StringDataEntry;
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.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
@ -98,7 +97,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
ruleNode.setName("Simple Rule Node");
ruleNode.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration.setFetchTo(TbMsgSource.METADATA);
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.relation.EntityRelation;
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.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
@ -463,7 +462,7 @@ public class HousekeeperServiceTest extends AbstractControllerTest {
ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
@ -472,7 +471,7 @@ public class HousekeeperServiceTest extends AbstractControllerTest {
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
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.relation.EntityRelation;
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.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
@ -472,7 +471,7 @@ public class ExportImportServiceSqlTest extends AbstractControllerTest {
RuleNode ruleNode1 = new RuleNode();
ruleNode1.setName("Generator 1");
ruleNode1.setType(TbMsgGeneratorNode.class.getName());
ruleNode1.setDebugStrategy(DebugStrategy.ALL_EVENTS);
ruleNode1.setDebugAllUntil(System.currentTimeMillis());
TbMsgGeneratorNodeConfiguration configuration1 = new TbMsgGeneratorNodeConfiguration();
configuration1.setOriginatorType(originatorId.getEntityType());
configuration1.setOriginatorId(originatorId.getId().toString());
@ -482,7 +481,7 @@ public class ExportImportServiceSqlTest extends AbstractControllerTest {
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));
@ -511,7 +510,7 @@ public class ExportImportServiceSqlTest extends AbstractControllerTest {
ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
@ -520,7 +519,7 @@ public class ExportImportServiceSqlTest extends AbstractControllerTest {
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
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.relation.EntityRelation;
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.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
@ -871,7 +870,7 @@ public class VersionControlTest extends AbstractControllerTest {
RuleNode ruleNode1 = new RuleNode();
ruleNode1.setName("Generator 1");
ruleNode1.setType(TbMsgGeneratorNode.class.getName());
ruleNode1.setDebugStrategy(DebugStrategy.ALL_EVENTS);
ruleNode1.setDebugAll(true);
TbMsgGeneratorNodeConfiguration configuration1 = new TbMsgGeneratorNodeConfiguration();
configuration1.setOriginatorType(originatorId.getEntityType());
configuration1.setOriginatorId(originatorId.getId().toString());
@ -881,7 +880,7 @@ public class VersionControlTest extends AbstractControllerTest {
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));
@ -909,7 +908,7 @@ public class VersionControlTest extends AbstractControllerTest {
ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
@ -918,7 +917,7 @@ public class VersionControlTest extends AbstractControllerTest {
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
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();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
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 maxResourceSize;
boolean mobileQrEnabled;
int maxRuleNodeDebugDurationMinutes;
int maxDebugModeDurationMinutes;
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.extern.slf4j.Slf4j;
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.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
@ -34,7 +35,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
@Slf4j
@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;
@ -47,10 +48,12 @@ public class RuleNode extends BaseDataWithAdditionalInfo<RuleNodeId> implements
@Length(fieldName = "name")
@Schema(description = "User defined name of the rule node. Used on UI and for logging. ", example = "Process sensor reading")
private String name;
@Schema(description = "Timestamp of the last rule node update.")
private long lastUpdateTs;
@Schema(description = "Debug strategy. ", example = "ALL_EVENTS")
private DebugStrategy debugStrategy;
@Schema(description = "Debug failures. ", example = "false")
private boolean debugFailures;
@Schema(description = "Debug All. Used as a trigger for updating debugAllUntil.", example = "false")
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")
private boolean singletonMode;
@Schema(description = "Queue name. ", example = "Main")
@ -77,8 +80,9 @@ public class RuleNode extends BaseDataWithAdditionalInfo<RuleNodeId> implements
this.ruleChainId = ruleNode.getRuleChainId();
this.type = ruleNode.getType();
this.name = ruleNode.getName();
this.lastUpdateTs = ruleNode.getLastUpdateTs();
this.debugStrategy = ruleNode.getDebugStrategy();
this.debugFailures = ruleNode.isDebugFailures();
this.debugAll = ruleNode.isDebugAll();
this.debugAllUntil = ruleNode.getDebugAllUntil();
this.singletonMode = ruleNode.isSingletonMode();
this.setConfiguration(ruleNode.getConfiguration());
this.externalId = ruleNode.getExternalId();
@ -89,10 +93,6 @@ public class RuleNode extends BaseDataWithAdditionalInfo<RuleNodeId> implements
return name;
}
public DebugStrategy getDebugStrategy() {
return debugStrategy == null ? DebugStrategy.DISABLED : debugStrategy;
}
public JsonNode getConfiguration() {
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")
private int maxRuleNodeExecutionsPerMessage;
@Schema(example = "15")
private int maxRuleNodeDebugDurationMinutes;
private int maxDebugModeDurationMinutes;
@Schema(example = "0")
private long maxEmails;
@Schema(example = "true")
@ -204,7 +204,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
}
@Override
public int getMaxRuleNodeDebugModeDurationMinutes(int systemMaxRuleNodeDebugModeDurationMinutes) {
return Math.min(systemMaxRuleNodeDebugModeDurationMinutes, maxRuleNodeDebugDurationMinutes);
public int getMaxDebugModeDurationMinutes(int systemMaxDebugModeDurationMinutes) {
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();
@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;
}
enum DebugStrategy {
DISABLED = 0;
ALL_EVENTS = 1;
ALL_THEN_ONLY_FAILURE_EVENTS = 2;
ONLY_FAILURE_EVENTS = 3;
}
message RuleNodeProto {
option deprecated = true;
int64 idMSB = 1;
@ -180,8 +173,8 @@ message RuleNodeProto {
string additionalInfo = 7;
bool singletonMode = 8;
int32 configurationVersion = 9;
int64 lastUpdateTs = 10;
DebugStrategy debugStrategy = 11;
bool debugFailures = 10;
int64 debugAllUntil = 11;
}
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 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 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.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.RuleChainId;
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.dao.DaoUtil;
import org.thingsboard.server.dao.model.BaseSqlEntity;
@ -61,12 +58,11 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> {
@Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
@Column(name = ModelConstants.LAST_UPDATE_TS_COLUMN)
private long lastUpdateTs;
@Column(name = ModelConstants.DEBUG_FAILURES)
private boolean debugFailures;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.DEBUG_STRATEGY)
private DebugStrategy debugStrategy;
@Column(name = ModelConstants.DEBUG__ALL_UNTIL)
private long debugAllUntil;
@Column(name = ModelConstants.SINGLETON_MODE)
private boolean singletonMode;
@ -90,8 +86,8 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> {
}
this.type = ruleNode.getType();
this.name = ruleNode.getName();
this.lastUpdateTs = ruleNode.getLastUpdateTs();
this.debugStrategy = ruleNode.getDebugStrategy();
this.debugFailures = ruleNode.isDebugFailures();
this.debugAllUntil = ruleNode.getDebugAllUntil();
this.singletonMode = ruleNode.isSingletonMode();
this.queueName = ruleNode.getQueueName();
this.configurationVersion = ruleNode.getConfigurationVersion();
@ -111,8 +107,8 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> {
}
ruleNode.setType(type);
ruleNode.setName(name);
ruleNode.setLastUpdateTs(lastUpdateTs);
ruleNode.setDebugStrategy(debugStrategy);
ruleNode.setDebugFailures(debugFailures);
ruleNode.setDebugAllUntil(debugAllUntil);
ruleNode.setSingletonMode(singletonMode);
ruleNode.setQueueName(queueName);
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")
.type(NotificationType.GENERAL)
.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)
.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.hibernate.exception.ConstraintViolationException;
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.transaction.annotation.Transactional;
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.relation.EntityRelation;
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.RuleChain;
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.Validator;
import org.thingsboard.server.dao.service.validator.RuleChainDataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import java.util.ArrayList;
import java.util.Collection;
@ -77,6 +79,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -110,6 +113,13 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
@Autowired
private DataValidator<RuleChain> ruleChainValidator;
@Autowired
@Lazy
private TbTenantProfileCache tbTenantProfileCache;
@Value("${debug_mode.max_duration:60}")
private int maxDebugModeDurationMinutes;
@Override
@Transactional
public RuleChain saveRuleChain(RuleChain ruleChain) {
@ -215,11 +225,20 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
}
RuleChainId ruleChainId = ruleChain.getId();
if (nodes != null) {
long lastUpdateTs = System.currentTimeMillis();
long now = System.currentTimeMillis();
for (RuleNode node : toAddOrUpdate) {
node.setRuleChainId(ruleChainId);
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);
RuleNode savedNode = ruleNodeDao.save(tenantId, node);
relations.add(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(),
@ -264,7 +283,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
layout.remove("description");
layout.remove("ruleChainNodeId");
targetNode.setAdditionalInfo(layout);
targetNode.setDebugStrategy(DebugStrategy.DISABLED);
targetNode = ruleNodeDao.save(tenantId, targetNode);
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),
type varchar(255),
name varchar(255),
last_update_ts bigint NOT NULL,
debug_strategy varchar(32) DEFAULT 'DISABLED',
debug_failures boolean,
debug_all_until bigint NOT NULL,
singleton_mode boolean,
queue_name varchar(255),
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 DOUBLE_KEY = "doubleKey";
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();
@ -102,7 +103,7 @@ public class LatestTimeseriesPerformanceTest extends AbstractServiceTest {
futures.add(save(generateDblEntry(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 totalTime = endTime - startTime;
@ -120,7 +121,7 @@ public class LatestTimeseriesPerformanceTest extends AbstractServiceTest {
futures.add(save(generateDblEntry(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) {

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

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

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

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

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

@ -8,7 +8,8 @@
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "msgTypeSwitch",
"debugStrategy": "ALL_EVENTS",
"debugFailures": false,
"debugAll": true,
"configuration": {
"version": 0
}
@ -20,7 +21,8 @@
},
"type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode",
"name": "formResponse",
"debugStrategy": "ALL_EVENTS",
"debugFailures": false,
"debugAll": true,
"configuration": {
"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",
"name": "rpcReply",
"debugStrategy": "ALL_EVENTS",
"debugFailures": false,
"debugAll": true,
"configuration": {
"requestIdMetaDataAttribute": "requestId"
}

2
msa/js-executor/docker/Dockerfile

@ -14,7 +14,7 @@
# 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 DOCKER_MODE true

48
msa/js-executor/package.json

@ -6,26 +6,26 @@
"main": "server.ts",
"bin": "server.js",
"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",
"start": "nodemon --watch '.' --ext 'ts' --exec 'ts-node server.ts'",
"start-prod": "nodemon --watch '.' --ext 'ts' --exec 'NODE_ENV=production ts-node server.ts'",
"build": "tsc"
},
"dependencies": {
"@aws-sdk/client-sqs": "^3.121.0",
"@azure/service-bus": "^7.5.1",
"@google-cloud/pubsub": "^4.3.3",
"amqplib": "^0.10.0",
"config": "^3.3.7",
"express": "^4.18.1",
"@aws-sdk/client-sqs": "^3.682.0",
"@azure/service-bus": "^7.9.5",
"@google-cloud/pubsub": "^4.8.0",
"amqplib": "^0.10.4",
"config": "^3.3.12",
"express": "^4.21.1",
"js-yaml": "^4.1.0",
"kafkajs": "^2.1.0",
"long": "^5.2.0",
"kafkajs": "^2.2.4",
"long": "^5.2.3",
"uuid-parse": "^1.1.0",
"uuid-random": "^1.3.2",
"winston": "^3.7.2",
"winston-daily-rotate-file": "^4.7.1"
"winston": "^3.16.0",
"winston-daily-rotate-file": "^5.0.0"
},
"nyc": {
"exclude": [
@ -36,26 +36,20 @@
]
},
"devDependencies": {
"@types/amqplib": "^0.8.2",
"@types/config": "^0.0.41",
"@types/express": "^4.17.13",
"@types/node": "^18.0.1",
"@types/uuid-parse": "^1.0.0",
"fs-extra": "^10.1.0",
"nodemon": "^2.0.16",
"pkg": "^5.7.0",
"ts-node": "^10.8.1",
"typescript": "^4.7.4"
"@types/amqplib": "^0.10.5",
"@types/config": "^3.3.5",
"@types/express": "~4.17.21",
"@types/node": "~20.17.6",
"@types/uuid-parse": "^1.0.2",
"fs-extra": "^11.2.0",
"nodemon": "^3.1.7",
"pkg": "^5.8.1",
"ts-node": "^10.9.2",
"typescript": "5.5.4"
},
"pkg": {
"assets": [
"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>
</goals>
<configuration>
<nodeVersion>v16.20.2</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion>
<nodeVersion>v20.18.0</nodeVersion>
<yarnVersion>v1.22.22</yarnVersion>
</configuration>
</execution>
<execution>

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

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

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

@ -57,7 +57,7 @@ export class ServiceBusTemplate implements IQueue {
this.parseQueueProperties();
const listQueues = await this.serviceBusService.listQueues();
const listQueues = this.serviceBusService.listQueues();
for await (const queue of listQueues) {
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:
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).
in_memory:
stats:

2
msa/web-ui/docker/Dockerfile

@ -14,7 +14,7 @@
# 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 DOCKER_MODE true

39
msa/web-ui/package.json

@ -6,22 +6,22 @@
"main": "server.ts",
"bin": "server.js",
"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",
"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'",
"build": "tsc"
},
"dependencies": {
"compression": "^1.7.4",
"config": "^3.3.7",
"compression": "^1.7.5",
"config": "^3.3.12",
"connect-history-api-fallback": "^1.6.0",
"express": "^4.18.1",
"express": "^4.21.1",
"http": "0.0.0",
"http-proxy": "^1.18.1",
"js-yaml": "^4.1.0",
"winston": "^3.7.2",
"winston-daily-rotate-file": "^4.7.1"
"winston": "^3.16.0",
"winston-daily-rotate-file": "^5.0.0"
},
"nyc": {
"exclude": [
@ -32,26 +32,21 @@
]
},
"devDependencies": {
"@types/compression": "^1.7.2",
"@types/config": "^0.0.41",
"@types/connect-history-api-fallback": "^1.3.5",
"@types/express": "^4.17.13",
"@types/http-proxy": "^1.17.9",
"@types/node": "^18.0.0",
"fs-extra": "^10.1.0",
"nodemon": "^2.0.16",
"pkg": "^5.7.0",
"ts-node": "^10.8.1",
"typescript": "^4.7.4"
"@types/compression": "^1.7.5",
"@types/config": "^3.3.5",
"@types/connect-history-api-fallback": "^1.5.4",
"@types/express": "~4.17.21",
"@types/http-proxy": "^1.17.15",
"@types/node": "~20.17.6",
"fs-extra": "^11.2.0",
"nodemon": "^3.1.7",
"pkg": "^5.8.1",
"ts-node": "^10.9.2",
"typescript": "5.5.4"
},
"pkg": {
"assets": [
"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>
</goals>
<configuration>
<nodeVersion>v16.20.2</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion>
<nodeVersion>v20.18.0</nodeVersion>
<yarnVersion>v1.22.22</yarnVersion>
</configuration>
</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,
name = "originator telemetry",
configClazz = TbGetTelemetryNodeConfiguration.class,
version = 1,
version = 2,
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 " +
"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 {
boolean hasChanges = false;
switch (fromVersion) {
case 0 -> {
case 0: {
if (oldConfiguration.hasNonNull("fetchMode")) {
String fetchMode = oldConfiguration.get("fetchMode").asText();
switch (fetchMode) {
case "FIRST":
case "FIRST" -> {
((ObjectNode) oldConfiguration).put("orderBy", Direction.ASC.name());
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true;
break;
case "LAST":
}
case "LAST" -> {
((ObjectNode) oldConfiguration).put("orderBy", Direction.DESC.name());
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true;
break;
case "ALL":
}
case "ALL" -> {
if (oldConfiguration.has("orderBy") &&
(oldConfiguration.get("orderBy").isNull() || oldConfiguration.get("orderBy").asText().isEmpty())) {
((ObjectNode) oldConfiguration).put("orderBy", Direction.ASC.name());
@ -257,16 +257,33 @@ public class TbGetTelemetryNode implements TbNode {
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true;
}
break;
default:
}
default -> {
((ObjectNode) oldConfiguration).put("fetchMode", FetchMode.LAST.name());
((ObjectNode) oldConfiguration).put("orderBy", Direction.DESC.name());
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
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);
}

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,
"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:
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).
kafka:
# Kafka Bootstrap Servers

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

@ -202,7 +202,9 @@ transport:
# Queue configuration parameters
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) .
kafka:
# Kafka Bootstrap Servers

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

@ -302,7 +302,9 @@ transport:
# Queue configuration properties
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).
kafka:
# Kafka Bootstrap Servers

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

@ -235,7 +235,9 @@ transport:
# Queue configuration parameters
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).
kafka:
# Kafka Bootstrap Servers

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

@ -181,7 +181,9 @@ transport:
# Queue configuration parameters
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).
kafka:
# Kafka Bootstrap Servers

2
ui-ngx/pom.xml

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

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

@ -22,11 +22,11 @@ import { MatDialog } from '@angular/material/dialog';
import {
EntityConflictDialogComponent
} from '@shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component';
import { HasId } from '@shared/models/base-data';
import { HasVersion } from '@shared/models/entity.models';
import { EntityInfoData, VersionedEntity } from '@shared/models/entity.models';
import { getInterceptorConfig } from './interceptor.util';
import { isDefined } from '@core/utils';
import { InterceptorConfig } from '@core/interceptors/interceptor-config';
import { RuleChainMetaData } from '@shared/models/rule-chain.models';
@Injectable()
export class EntityConflictInterceptor implements HttpInterceptor {
@ -35,7 +35,7 @@ export class EntityConflictInterceptor implements HttpInterceptor {
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/')) {
return next.handle(request);
}
@ -52,11 +52,11 @@ export class EntityConflictInterceptor implements HttpInterceptor {
}
private handleConflictError(
request: HttpRequest<unknown & HasId & HasVersion>,
request: HttpRequest<VersionedEntity>,
next: HttpHandler,
error: HttpErrorResponse
): Observable<HttpEvent<unknown>> {
if (getInterceptorConfig(request).ignoreVersionConflict) {
if (getInterceptorConfig(request).ignoreVersionConflict || !this.isVersionedEntity(request.body)) {
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 };
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, {
disableClose: true,
data: { message, entity },

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

@ -73,7 +73,7 @@
</mat-chip-listbox>
</div>
<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"
formControlName="typeList"
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-value {
max-width: 200px;
font-size: 14px;
}
.mat-mdc-select-arrow {
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-value {
max-width: 200px;
font-size: 14px;
}
.mat-mdc-select-arrow {
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 {
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 {

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

@ -43,7 +43,7 @@
</div>
<div class="tb-hint">{{ hintText | translate }}</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 class="flex max-w-35% flex-full flex-col">
<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))}"
(mousedown)="$event.stopPropagation()">
<button mat-icon-button *ngFor="let action of widget.customHeaderActions"
type="button"
[class.!hidden]="isEdit"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
@ -45,6 +46,7 @@
<tb-icon>{{ action.icon }}</tb-icon>
</button>
<button mat-icon-button *ngFor="let action of widget.widgetActions"
type="button"
[class.!hidden]="isEdit || !action.show"
(click)="action.onAction($event)"
matTooltip="{{ action.name | translate }}"
@ -52,6 +54,7 @@
<tb-icon>{{ action.icon }}</tb-icon>
</button>
<button mat-icon-button
type="button"
[class.!hidden]="isEdit || !widget.enableFullscreen"
(click)="$event.stopPropagation(); widget.isFullscreen = !widget.isFullscreen; updateEditWidgetActionsTooltipState()"
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) {
if (event) {
onClicked(event: MouseEvent): void {
if (event && this.isEdit) {
event.stopPropagation();
}
this.widgetComponentAction.emit({
@ -223,8 +223,8 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
});
}
onContextMenu(event: TbContextMenuEvent) {
if (event) {
onContextMenu(event: TbContextMenuEvent): void {
if (event && this.isEdit) {
event.stopPropagation();
}
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;
if (type === MapperType.CUSTOM) {
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 {
mapperConfig.removeControl('custom');
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"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'manageEdges')"
[class.!hidden]="isEdit">
[class.!hidden]="isEdit"
*ngIf="edgesSupportEnabled()">
{{'customer.manage-edges' | translate }}
</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>> = [];
columns.push(
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') {
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">
<h2 translate>widget.select-widget-type</h2>
<span class="flex-1"></span>
<div tb-help="widgetTypes"></div>
<button mat-icon-button
(click)="cancel()"
type="button">

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

@ -15,7 +15,7 @@
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>
<input matInput type="text"
#countryInput

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

@ -28,7 +28,7 @@ import {
import { isNotEmptyStr } from '@core/utils';
import { Observable, of } from 'rxjs';
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 { TranslateService } from '@ngx-translate/core';
@ -71,6 +71,9 @@ export class CountryAutocompleteComponent implements OnInit, ControlValueAccesso
@coerceBoolean()
required = false;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Input()
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 { CommonModule } from '@angular/common';
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 { RuleChainMetaData } from '@shared/models/rule-chain.models';
interface EntityConflictDialogData {
message: string;
entity: EntityInfoData | RuleChainMetaData;
entity: VersionedEntity;
}
@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 { WidgetService } from '@core/http/widget.service';
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 { RuleChain, RuleChainImport, RuleChainMetaData, RuleChainType } from '@shared/models/rule-chain.models';
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;
let fileName = (entityData as EntityInfoData).name;
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 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 = {
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>',
@ -387,6 +391,67 @@ export const serviceCompletions: TbEditorCompletions = {
],
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: {
description: 'Get asset by id',
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 { Lwm2mSecurityConfigModels } from '@shared/models/lwm2m-security-config.models';
import { TenantId } from '@shared/models/id/tenant-id';
import { RuleChainMetaData } from '@shared/models/rule-chain.models';
export interface EntityInfo {
name?: string;
@ -197,3 +198,5 @@ export interface HasDebugConfig {
debugFailures?: boolean;
debugAllUntil?: number;
}
export type VersionedEntity = EntityInfoData & HasVersion | RuleChainMetaData;

4
ui-ngx/src/styles.scss

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

Loading…
Cancel
Save