From 4bbcffdf637f7fca0aa2caa03c514f31a3872561 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 29 Mar 2018 20:25:47 +0300 Subject: [PATCH] Rule Node Debug UI --- .../server/actors/ActorSystemContext.java | 71 +++++++++++-------- .../AbstractRuleEngineControllerTest.java | 2 +- .../server/common/data/DataConstants.java | 2 +- ui/src/app/common/types.constant.js | 30 +++++++- .../components/details-sidenav.directive.js | 19 ++++- .../app/components/details-sidenav.tpl.html | 1 + .../event/event-content-dialog.controller.js | 17 ++++- .../event-header-debug-rulenode.tpl.html | 27 +++++++ ui/src/app/event/event-header.directive.js | 7 ++ .../event/event-row-debug-rulenode.tpl.html | 63 ++++++++++++++++ ui/src/app/event/event-row.directive.js | 16 ++++- ui/src/app/event/event-table.directive.js | 18 ++++- ui/src/app/locale/locale.constant.js | 15 ++++ ui/src/app/rulechain/rulechain.controller.js | 15 ++-- ui/src/app/rulechain/rulechain.tpl.html | 18 ++++- ui/src/app/rulechain/rulechains.tpl.html | 3 +- 16 files changed, 276 insertions(+), 48 deletions(-) create mode 100644 ui/src/app/event/event-header-debug-rulenode.tpl.html create mode 100644 ui/src/app/event/event-row-debug-rulenode.tpl.html diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index a5a20b83fa..9e02946574 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -25,6 +25,7 @@ import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; @@ -60,11 +62,13 @@ import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; import org.thingsboard.server.service.component.ComponentDiscoveryService; +import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.Optional; +@Slf4j @Component public class ActorSystemContext { private static final String AKKA_CONF_FILE_NAME = "actor-system.conf"; @@ -292,38 +296,49 @@ public class ActorSystemContext { } private void persistDebug(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, Throwable error) { - Event event = new Event(); - event.setTenantId(tenantId); - event.setEntityId(entityId); - event.setType(DataConstants.DEBUG); - - ObjectNode node = mapper.createObjectNode() - .put("type", type) - .put("server", getServerAddress()) - .put("entityId", tbMsg.getOriginator().getId().toString()) - .put("entityName", tbMsg.getOriginator().getEntityType().name()) - .put("msgId", tbMsg.getId().toString()) - .put("msgType", tbMsg.getType()) - .put("dataType", tbMsg.getDataType().name()); - - ObjectNode mdNode = node.putObject("metadata"); - tbMsg.getMetaData().getData().forEach(mdNode::put); + try { + Event event = new Event(); + event.setTenantId(tenantId); + event.setEntityId(entityId); + event.setType(DataConstants.DEBUG_RULE_NODE); + + String metadata = mapper.writeValueAsString(tbMsg.getMetaData().getData()); + + ObjectNode node = mapper.createObjectNode() + .put("type", type) + .put("server", getServerAddress()) + .put("entityId", tbMsg.getOriginator().getId().toString()) + .put("entityName", tbMsg.getOriginator().getEntityType().name()) + .put("msgId", tbMsg.getId().toString()) + .put("msgType", tbMsg.getType()) + .put("dataType", tbMsg.getDataType().name()) + .put("data", convertToString(tbMsg.getDataType(), tbMsg.getData())) + .put("metadata", metadata); + + if (error != null) { + node = node.put("error", toString(error)); + } + + event.setBody(node); + eventService.save(event); + } catch (IOException ex) { + log.warn("Failed to persist rule node debug message", ex); + } + } - switch (tbMsg.getDataType()) { + private String convertToString(TbMsgDataType messageType, byte[] data) { + if (data == null) { + return null; + } + switch (messageType) { + case JSON: + case TEXT: + return new String(data, StandardCharsets.UTF_8); case BINARY: - node.put("data", Base64Utils.encodeUrlSafe(tbMsg.getData())); - break; + return Base64Utils.encodeToString(data); default: - node.put("data", new String(tbMsg.getData(), StandardCharsets.UTF_8)); - break; - } - - if (error != null) { - node = node.put("error", toString(error)); + throw new RuntimeException("Message type: " + messageType + " is not supported!"); } - - event.setBody(node); - eventService.save(event); } public static Exception toException(Throwable error) { diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java index bbcb98ff48..93fe76752e 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java @@ -51,6 +51,6 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest { TimePageLink pageLink = new TimePageLink(limit); return doGetTypedWithTimePageLink("/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&", new TypeReference>() { - }, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG, tenantId.getId()); + }, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG_RULE_NODE, tenantId.getId()); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 659a242882..7d4e4807c9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -37,7 +37,7 @@ public class DataConstants { public static final String ERROR = "ERROR"; public static final String LC_EVENT = "LC_EVENT"; public static final String STATS = "STATS"; - public static final String DEBUG = "DEBUG"; + public static final String DEBUG_RULE_NODE = "DEBUG_RULE_NODE"; public static final String ONEWAY = "ONEWAY"; public static final String TWOWAY = "TWOWAY"; diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 8026115b35..21865087cc 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -279,6 +279,23 @@ export default angular.module('thingsboard.types', []) function: "function", alarm: "alarm" }, + contentType: { + "JSON": { + value: "JSON", + name: "content-type.json", + code: "json" + }, + "TEXT": { + value: "TEXT", + name: "content-type.text", + code: "text" + }, + "BINARY": { + value: "BINARY", + name: "content-type.binary", + code: "text" + } + }, componentType: { filter: "FILTER", processor: "PROCESSOR", @@ -295,7 +312,8 @@ export default angular.module('thingsboard.types', []) user: "USER", dashboard: "DASHBOARD", alarm: "ALARM", - rulechain: "RULE_CHAIN" + rulechain: "RULE_CHAIN", + rulenode: "RULE_NODE" }, aliasEntityType: { current_customer: "CURRENT_CUSTOMER" @@ -388,6 +406,16 @@ export default angular.module('thingsboard.types', []) name: "event.type-stats" } }, + debugEventType: { + debugRuleNode: { + value: "DEBUG_RULE_NODE", + name: "event.type-debug-rule-node" + }, + debugRuleChain: { + value: "DEBUG_RULE_CHAIN", + name: "event.type-debug-rule-chain" + } + }, extensionType: { http: "HTTP", mqtt: "MQTT", diff --git a/ui/src/app/components/details-sidenav.directive.js b/ui/src/app/components/details-sidenav.directive.js index e455a80dcd..a25374b099 100644 --- a/ui/src/app/components/details-sidenav.directive.js +++ b/ui/src/app/components/details-sidenav.directive.js @@ -26,7 +26,7 @@ export default angular.module('thingsboard.directives.detailsSidenav', []) .name; /*@ngInject*/ -function DetailsSidenav($timeout) { +function DetailsSidenav($timeout, $window) { var linker = function (scope, element, attrs) { @@ -42,6 +42,23 @@ function DetailsSidenav($timeout) { scope.isEdit = true; } + if (angular.isDefined(attrs.closeOnClickOutside && attrs.closeOnClickOutside)) { + scope.closeOnClickOutside = true; + var clickOutsideHandler = function() { + scope.closeDetails(); + }; + angular.element($window).click(clickOutsideHandler); + scope.$on("$destroy", function () { + angular.element($window).unbind('click', clickOutsideHandler); + }); + } + + scope.onClick = function($event) { + if (scope.closeOnClickOutside) { + $event.stopPropagation(); + } + }; + scope.toggleDetailsEditMode = function () { if (!scope.isAlwaysEdit) { if (!scope.isEdit) { diff --git a/ui/src/app/components/details-sidenav.tpl.html b/ui/src/app/components/details-sidenav.tpl.html index c504a24a79..a0032ff435 100644 --- a/ui/src/app/components/details-sidenav.tpl.html +++ b/ui/src/app/components/details-sidenav.tpl.html @@ -19,6 +19,7 @@ md-disable-backdrop="true" md-is-open="isOpen" md-component-id="right" + ng-click="onClick($event)" layout="column">
diff --git a/ui/src/app/event/event-content-dialog.controller.js b/ui/src/app/event/event-content-dialog.controller.js index 108f95e323..8d13f9654a 100644 --- a/ui/src/app/event/event-content-dialog.controller.js +++ b/ui/src/app/event/event-content-dialog.controller.js @@ -17,11 +17,14 @@ import $ from 'jquery'; import 'brace/ext/language_tools'; import 'brace/mode/java'; import 'brace/theme/github'; +import beautify from 'js-beautify'; /* eslint-disable angular/angularelement */ +const js_beautify = beautify.js; + /*@ngInject*/ -export default function EventContentDialogController($mdDialog, content, title, showingCallback) { +export default function EventContentDialogController($mdDialog, types, content, contentType, title, showingCallback) { var vm = this; @@ -32,9 +35,19 @@ export default function EventContentDialogController($mdDialog, content, title, vm.content = content; vm.title = title; + var mode; + if (contentType) { + mode = types.contentType[contentType].code; + if (contentType == types.contentType.JSON.value && vm.content) { + vm.content = js_beautify(vm.content, {indent_size: 4}); + } + } else { + mode = 'java'; + } + vm.contentOptions = { useWrapMode: false, - mode: 'java', + mode: mode, showGutter: false, showPrintMargin: false, theme: 'github', diff --git a/ui/src/app/event/event-header-debug-rulenode.tpl.html b/ui/src/app/event/event-header-debug-rulenode.tpl.html new file mode 100644 index 0000000000..b412a0c50a --- /dev/null +++ b/ui/src/app/event/event-header-debug-rulenode.tpl.html @@ -0,0 +1,27 @@ + +
event.event-time
+
event.server
+
event.type
+
event.entity
+
event.message-id
+
event.message-type
+
event.data-type
+
event.data
+
event.metadata
+
event.error
diff --git a/ui/src/app/event/event-header.directive.js b/ui/src/app/event/event-header.directive.js index afac804f30..bc4cdbe9ea 100644 --- a/ui/src/app/event/event-header.directive.js +++ b/ui/src/app/event/event-header.directive.js @@ -18,6 +18,7 @@ import eventHeaderLcEventTemplate from './event-header-lc-event.tpl.html'; import eventHeaderStatsTemplate from './event-header-stats.tpl.html'; import eventHeaderErrorTemplate from './event-header-error.tpl.html'; +import eventHeaderDebugRuleNodeTemplate from './event-header-debug-rulenode.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ @@ -38,6 +39,12 @@ export default function EventHeaderDirective($compile, $templateCache, types) { case types.eventType.error.value: template = eventHeaderErrorTemplate; break; + case types.debugEventType.debugRuleNode.value: + template = eventHeaderDebugRuleNodeTemplate; + break; + case types.debugEventType.debugRuleChain.value: + template = eventHeaderDebugRuleNodeTemplate; + break; } return $templateCache.get(template); } diff --git a/ui/src/app/event/event-row-debug-rulenode.tpl.html b/ui/src/app/event/event-row-debug-rulenode.tpl.html new file mode 100644 index 0000000000..ec00b39ef1 --- /dev/null +++ b/ui/src/app/event/event-row-debug-rulenode.tpl.html @@ -0,0 +1,63 @@ + +
{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}
+
{{event.body.server}}
+
{{event.body.type}}
+
{{event.body.entityName}}
+
{{event.body.msgId}}
+
{{event.body.msgType}}
+
{{event.body.dataType}}
+
+ + + {{ 'action.view' | translate }} + + + more_horiz + + +
+
+ + + {{ 'action.view' | translate }} + + + more_horiz + + +
+
+ + + {{ 'action.view' | translate }} + + + more_horiz + + +
diff --git a/ui/src/app/event/event-row.directive.js b/ui/src/app/event/event-row.directive.js index f005542d40..4643761b39 100644 --- a/ui/src/app/event/event-row.directive.js +++ b/ui/src/app/event/event-row.directive.js @@ -20,6 +20,7 @@ import eventErrorDialogTemplate from './event-content-dialog.tpl.html'; import eventRowLcEventTemplate from './event-row-lc-event.tpl.html'; import eventRowStatsTemplate from './event-row-stats.tpl.html'; import eventRowErrorTemplate from './event-row-error.tpl.html'; +import eventRowDebugRuleNodeTemplate from './event-row-debug-rulenode.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ @@ -40,6 +41,12 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $ case types.eventType.error.value: template = eventRowErrorTemplate; break; + case types.debugEventType.debugRuleNode.value: + template = eventRowDebugRuleNodeTemplate; + break; + case types.debugEventType.debugRuleChain.value: + template = eventRowDebugRuleNodeTemplate; + break; } return $templateCache.get(template); } @@ -53,17 +60,22 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $ scope.loadTemplate(); }); + scope.types = types; + scope.event = attrs.event; - scope.showContent = function($event, content, title) { + scope.showContent = function($event, content, title, contentType) { var onShowingCallback = { onShowing: function(){} } + if (!contentType) { + contentType = null; + } $mdDialog.show({ controller: 'EventContentDialogController', controllerAs: 'vm', templateUrl: eventErrorDialogTemplate, - locals: {content: content, title: title, showingCallback: onShowingCallback}, + locals: {content: content, title: title, contentType: contentType, showingCallback: onShowingCallback}, parent: angular.element($document[0].body), fullscreen: true, targetEvent: $event, diff --git a/ui/src/app/event/event-table.directive.js b/ui/src/app/event/event-table.directive.js index 4291014340..c61078dfe6 100644 --- a/ui/src/app/event/event-table.directive.js +++ b/ui/src/app/event/event-table.directive.js @@ -36,8 +36,8 @@ export default function EventTableDirective($compile, $templateCache, $rootScope for (var type in types.eventType) { var eventType = types.eventType[type]; var enabled = true; - for (var disabledType in disabledEventTypes) { - if (eventType.value === disabledEventTypes[disabledType]) { + for (var i=0;i
- + -
+
+ + + +
+ debug-event-types="{{vm.types.debugEventType.debugRuleChain.value}}" + default-event-type="{{vm.types.debugEventType.debugRuleChain.value}}">