Browse Source

Test rule node script functions.

pull/725/head
Igor Kulikov 8 years ago
parent
commit
c2b353f47e
  1. 83
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  2. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  3. 13
      ui/src/app/api/rule-chain.service.js
  4. 119
      ui/src/app/components/kv-map.directive.js
  5. 26
      ui/src/app/components/kv-map.scss
  6. 58
      ui/src/app/components/kv-map.tpl.html
  7. 2
      ui/src/app/layout/index.js
  8. 17
      ui/src/app/locale/locale.constant.js
  9. 4
      ui/src/app/rulechain/index.js
  10. 177
      ui/src/app/rulechain/script/node-script-test.controller.js
  11. 108
      ui/src/app/rulechain/script/node-script-test.scss
  12. 81
      ui/src/app/rulechain/script/node-script-test.service.js
  13. 119
      ui/src/app/rulechain/script/node-script-test.tpl.html

83
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -15,9 +15,17 @@
*/
package org.thingsboard.server.controller;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.PluginId;
@ -30,17 +38,25 @@ import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.script.NashornJsEngine;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Slf4j
@RestController
@RequestMapping("/api")
public class RuleChainController extends BaseController {
public static final String RULE_CHAIN_ID = "ruleChainId";
private static final ObjectMapper objectMapper = new ObjectMapper();
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET)
@ResponseBody
@ -203,4 +219,71 @@ public class RuleChainController extends BaseController {
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST)
@ResponseBody
public JsonNode testScript(@RequestBody JsonNode inputParams) throws ThingsboardException {
try {
String script = inputParams.get("script").asText();
String scriptType = inputParams.get("scriptType").asText();
String functionName = inputParams.get("functionName").asText();
JsonNode argNamesJson = inputParams.get("argNames");
String[] argNames = objectMapper.treeToValue(argNamesJson, String[].class);
String data = inputParams.get("msg").asText();
JsonNode metadataJson = inputParams.get("metadata");
Map<String, String> metadata = objectMapper.convertValue(metadataJson, new TypeReference<Map<String, String>>() {});
String msgType = inputParams.get("msgType").asText();
String output = "";
String errorText = "";
ScriptEngine engine = null;
try {
engine = new NashornJsEngine(script, functionName, argNames);
TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data);
switch (scriptType) {
case "update":
output = msgToOutput(engine.executeUpdate(inMsg));
break;
case "generate":
output = msgToOutput(engine.executeGenerate(inMsg));
break;
case "filter":
boolean result = engine.executeFilter(inMsg);
output = Boolean.toString(result);
break;
case "switch":
Set<String> states = engine.executeSwitch(inMsg);
output = objectMapper.writeValueAsString(states);
break;
default:
throw new IllegalArgumentException("Unsupported script type: " + scriptType);
}
} catch (Exception e) {
log.error("Error evaluating JS function", e);
errorText = e.getMessage();
} finally {
if (engine != null) {
engine.destroy();
}
}
ObjectNode result = objectMapper.createObjectNode();
result.put("output", output);
result.put("error", errorText);
return result;
} catch (Exception e) {
throw handleException(e);
}
}
private String msgToOutput(TbMsg msg) throws Exception {
ObjectNode msgData = objectMapper.createObjectNode();
if (!StringUtils.isEmpty(msg.getData())) {
msgData.set("msg", objectMapper.readTree(msg.getData()));
}
Map<String, String> metadata = msg.getMetaData().getData();
msgData.set("metadata", objectMapper.valueToTree(metadata));
msgData.put("msgType", msg.getType());
return objectMapper.writeValueAsString(msgData);
}
}

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

13
ui/src/app/api/rule-chain.service.js

@ -33,7 +33,8 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
getRuleNodeComponents: getRuleNodeComponents,
getRuleNodeComponentByClazz: getRuleNodeComponentByClazz,
getRuleNodeSupportedLinks: getRuleNodeSupportedLinks,
resolveTargetRuleChains: resolveTargetRuleChains
resolveTargetRuleChains: resolveTargetRuleChains,
testScript: testScript
};
return service;
@ -292,5 +293,15 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
return componentDescriptorService.getComponentDescriptorsByTypes(types.ruleNodeTypeComponentTypes);
}
function testScript(inputParams) {
var deferred = $q.defer();
var url = '/api/ruleChain/testScript';
$http.post(url, inputParams).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
}

119
ui/src/app/components/kv-map.directive.js

@ -0,0 +1,119 @@
/*
* Copyright © 2016-2018 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.
*/
import './kv-map.scss';
/* eslint-disable import/no-unresolved, import/default */
import kvMapTemplate from './kv-map.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.keyValMap', [])
.directive('tbKeyValMap', KeyValMap)
.name;
/*@ngInject*/
function KeyValMap() {
return {
restrict: "E",
scope: true,
bindToController: {
disabled:'=ngDisabled',
titleText: '@?',
keyPlaceholderText: '@?',
valuePlaceholderText: '@?',
noDataText: '@?',
keyValMap: '='
},
controller: KeyValMapController,
controllerAs: 'vm',
templateUrl: kvMapTemplate
};
}
/*@ngInject*/
function KeyValMapController($scope, $mdUtil) {
let vm = this;
vm.kvList = [];
vm.removeKeyVal = removeKeyVal;
vm.addKeyVal = addKeyVal;
$scope.$watch('vm.keyValMap', () => {
stopWatchKvList();
vm.kvList.length = 0;
if (vm.keyValMap) {
for (var property in vm.keyValMap) {
if (vm.keyValMap.hasOwnProperty(property)) {
vm.kvList.push(
{
key: property + '',
value: vm.keyValMap[property]
}
);
}
}
}
$mdUtil.nextTick(() => {
watchKvList();
});
});
function watchKvList() {
$scope.kvListWatcher = $scope.$watch('vm.kvList', () => {
if (!vm.keyValMap) {
return;
}
for (var property in vm.keyValMap) {
if (vm.keyValMap.hasOwnProperty(property)) {
delete vm.keyValMap[property];
}
}
for (var i=0;i<vm.kvList.length;i++) {
var entry = vm.kvList[i];
vm.keyValMap[entry.key] = entry.value;
}
}, true);
}
function stopWatchKvList() {
if ($scope.kvListWatcher) {
$scope.kvListWatcher();
$scope.kvListWatcher = null;
}
}
function removeKeyVal(index) {
if (index > -1) {
vm.kvList.splice(index, 1);
}
}
function addKeyVal() {
if (!vm.kvList) {
vm.kvList = [];
}
vm.kvList.push(
{
key: '',
value: ''
}
);
}
}

26
ui/src/app/components/kv-map.scss

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2018 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.
*/
.tb-kv-map {
span.no-data-found {
position: relative;
height: 40px;
text-transform: uppercase;
display: flex;
&.disabled {
color: rgba(0,0,0,0.38);
}
}
}

58
ui/src/app/components/kv-map.tpl.html

@ -0,0 +1,58 @@
<!--
Copyright © 2016-2018 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.
-->
<section layout="column" class="tb-kv-map">
<label translate class="tb-title no-padding">{{ vm.titleText }}</label>
<div flex layout="row"
ng-repeat="keyVal in vm.kvList track by $index"
style="max-height: 40px;" layout-align="start center">
<md-input-container flex md-no-float class="md-block"
style="margin: 10px 0px 0px 0px; max-height: 40px;">
<input placeholder="{{ (vm.keyPlaceholderText ? vm.keyPlaceholderText : 'key-val.key') | translate }}"
ng-disabled="vm.disabled" ng-required="true" name="key" ng-model="keyVal.key">
</md-input-container>
<md-input-container flex md-no-float class="md-block"
style="margin: 10px 0px 0px 0px; max-height: 40px;">
<input placeholder="{{ (vm.valuePlaceholderText ? vm.valuePlaceholderText : 'key-val.value') | translate }}"
ng-disabled="vm.disabled" ng-required="true" name="value" ng-model="keyVal.value">
</md-input-container>
<md-button ng-show="!vm.disabled" ng-disabled="$root.loading" class="md-icon-button md-primary"
ng-click="vm.removeKeyVal($index)"
aria-label="{{ 'action.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'key-val.remove-entry' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.delete' | translate }}"
class="material-icons">
close
</md-icon>
</md-button>
</div>
<span ng-show="!vm.kvList.length"
layout-align="center center" ng-class="{'disabled': vm.disabled}"
class="no-data-found" translate>{{vm.noDataText ? vm.noDataText : 'key-val.no-data'}}</span>
<div>
<md-button ng-show="!vm.disabled" ng-disabled="$root.loading" class="md-primary md-raised"
ng-click="vm.addKeyVal()"
aria-label="{{ 'action.add' | translate }}">
<md-tooltip md-direction="top">
{{ 'key-val.add-entry' | translate }}
</md-tooltip>
<span translate>action.add</span>
</md-button>
</div>
</section>

2
ui/src/app/layout/index.js

@ -29,6 +29,7 @@ import thingsboardNoAnimate from '../components/no-animate.directive';
import thingsboardOnFinishRender from '../components/finish-render.directive';
import thingsboardSideMenu from '../components/side-menu.directive';
import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive';
import thingsboardKvMap from '../components/kv-map.directive';
import thingsboardJsonObjectEdit from '../components/json-object-edit.directive';
import thingsboardJsonContent from '../components/json-content.directive';
@ -93,6 +94,7 @@ export default angular.module('thingsboard.home', [
thingsboardOnFinishRender,
thingsboardSideMenu,
thingsboardDashboardAutocomplete,
thingsboardKvMap,
thingsboardJsonObjectEdit,
thingsboardJsonContent
])

17
ui/src/app/locale/locale.constant.js

@ -962,6 +962,13 @@ export default angular.module('thingsboard.locale', [])
"no-return-error": "Function must return value!",
"return-type-mismatch": "Function must return value of '{{type}}' type!"
},
"key-val": {
"key": "Key",
"value": "Value",
"remove-entry": "Remove entry",
"add-entry": "Add entry",
"no-data": "No entries"
},
"layout": {
"layout": "Layout",
"manage": "Manage layouts",
@ -1220,7 +1227,15 @@ export default angular.module('thingsboard.locale', [])
"type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
"directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
"ui-resources-load-error": "Failed to load configuration ui resources.",
"invalid-target-rulechain": "Unable to resolve target rule chain!"
"invalid-target-rulechain": "Unable to resolve target rule chain!",
"test-script-function": "Test script function",
"message": "Message",
"message-type": "Message type",
"message-type-required": "Message type is required",
"metadata": "Metadata",
"metadata-required": "Metadata entries can't be empty.",
"output": "Output",
"test": "Test"
},
"rule-plugin": {
"management": "Rules and plugins management"

4
ui/src/app/rulechain/index.js

@ -17,11 +17,13 @@
import RuleChainRoutes from './rulechain.routes';
import RuleChainsController from './rulechains.controller';
import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller';
import NodeScriptTestController from './script/node-script-test.controller';
import RuleChainDirective from './rulechain.directive';
import RuleNodeDefinedConfigDirective from './rulenode-defined-config.directive';
import RuleNodeConfigDirective from './rulenode-config.directive';
import RuleNodeDirective from './rulenode.directive';
import LinkDirective from './link.directive';
import NodeScriptTest from './script/node-script-test.service';
export default angular.module('thingsboard.ruleChain', [])
.config(RuleChainRoutes)
@ -29,9 +31,11 @@ export default angular.module('thingsboard.ruleChain', [])
.controller('RuleChainController', RuleChainController)
.controller('AddRuleNodeController', AddRuleNodeController)
.controller('AddRuleNodeLinkController', AddRuleNodeLinkController)
.controller('NodeScriptTestController', NodeScriptTestController)
.directive('tbRuleChain', RuleChainDirective)
.directive('tbRuleNodeDefinedConfig', RuleNodeDefinedConfigDirective)
.directive('tbRuleNodeConfig', RuleNodeConfigDirective)
.directive('tbRuleNode', RuleNodeDirective)
.directive('tbRuleNodeLink', LinkDirective)
.factory('ruleNodeScriptTest', NodeScriptTest)
.name;

177
ui/src/app/rulechain/script/node-script-test.controller.js

@ -0,0 +1,177 @@
/*
* Copyright © 2016-2018 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.
*/
import './node-script-test.scss';
import Split from 'split.js';
import beautify from 'js-beautify';
const js_beautify = beautify.js;
/*@ngInject*/
export default function NodeScriptTestController($scope, $mdDialog, $window, $document, $timeout,
$q, $mdUtil, $translate, toast, types, utils,
ruleChainService, onShowingCallback, msg, msgType, metadata,
functionTitle, inputParams) {
var vm = this;
vm.types = types;
vm.functionTitle = functionTitle;
vm.inputParams = inputParams;
vm.inputParams.msg = js_beautify(angular.toJson(msg), {indent_size: 4});
vm.inputParams.metadata = metadata;
vm.inputParams.msgType = msgType;
vm.output = '';
vm.test = test;
vm.save = save;
vm.cancel = cancel;
$scope.$watch('theForm.metadataForm.$dirty', (newVal) => {
if (newVal) {
toast.hide();
}
});
onShowingCallback.onShowed = () => {
vm.nodeScriptTestDialogElement = angular.element('.tb-node-script-test-dialog');
var w = vm.nodeScriptTestDialogElement.width();
if (w > 0) {
initSplitLayout();
} else {
$scope.$watch(
function () {
return vm.nodeScriptTestDialogElement[0].offsetWidth || parseInt(vm.nodeScriptTestDialogElement.css('width'), 10);
},
function (newSize) {
if (newSize > 0) {
initSplitLayout();
}
}
);
}
};
function onDividerDrag() {
$scope.$broadcast('update-ace-editor-size');
}
function initSplitLayout() {
if (!vm.layoutInited) {
Split([angular.element('#top_panel', vm.nodeScriptTestDialogElement)[0], angular.element('#bottom_panel', vm.nodeScriptTestDialogElement)[0]], {
sizes: [35, 65],
gutterSize: 8,
cursor: 'row-resize',
direction: 'vertical',
onDrag: function () {
onDividerDrag()
}
});
Split([angular.element('#top_left_panel', vm.nodeScriptTestDialogElement)[0], angular.element('#top_right_panel', vm.nodeScriptTestDialogElement)[0]], {
sizes: [50, 50],
gutterSize: 8,
cursor: 'col-resize',
onDrag: function () {
onDividerDrag()
}
});
Split([angular.element('#bottom_left_panel', vm.nodeScriptTestDialogElement)[0], angular.element('#bottom_right_panel', vm.nodeScriptTestDialogElement)[0]], {
sizes: [50, 50],
gutterSize: 8,
cursor: 'col-resize',
onDrag: function () {
onDividerDrag()
}
});
onDividerDrag();
$scope.$applyAsync(function () {
vm.layoutInited = true;
var w = angular.element($window);
$timeout(function () {
w.triggerHandler('resize')
});
});
}
}
function test() {
testNodeScript().then(
(output) => {
vm.output = js_beautify(output, {indent_size: 4});
}
);
}
function checkInputParamErrors() {
$scope.theForm.metadataForm.$setPristine();
$scope.$broadcast('form-submit', 'validatePayload');
if (!$scope.theForm.payloadForm.$valid) {
return false;
} else if (!$scope.theForm.metadataForm.$valid) {
showMetadataError($translate.instant('rulenode.metadata-required'));
return false;
}
return true;
}
function showMetadataError(error) {
var toastParent = angular.element('#metadata-panel', vm.nodeScriptTestDialogElement);
toast.showError(error, toastParent, 'bottom left');
}
function testNodeScript() {
var deferred = $q.defer();
if (checkInputParamErrors()) {
$mdUtil.nextTick(() => {
ruleChainService.testScript(vm.inputParams).then(
(result) => {
if (result.error) {
toast.showError(result.error);
deferred.reject();
} else {
deferred.resolve(result.output);
}
},
() => {
deferred.reject();
}
);
});
} else {
deferred.reject();
}
return deferred.promise;
}
function cancel() {
$mdDialog.cancel();
}
function save() {
testNodeScript().then(() => {
$scope.theForm.funcBodyForm.$setPristine();
$mdDialog.hide(vm.inputParams.script);
});
}
}

108
ui/src/app/rulechain/script/node-script-test.scss

@ -0,0 +1,108 @@
/**
* Copyright © 2016-2018 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.
*/
@import '../../../scss/constants';
@import "~compass-sass-mixins/lib/compass";
md-dialog.tb-node-script-test-dialog {
&.md-dialog-fullscreen {
min-height: 100%;
min-width: 100%;
border-radius: 0;
}
.tb-split {
@include box-sizing(border-box);
overflow-y: auto;
overflow-x: hidden;
}
.ace_editor {
font-size: 14px !important;
}
.tb-content {
border: 1px solid #C0C0C0;
padding-top: 5px;
padding-left: 5px;
}
.gutter {
background-color: #eeeeee;
background-repeat: no-repeat;
background-position: 50%;
}
.gutter.gutter-horizontal {
cursor: col-resize;
background-image: url('../../../../node_modules/split.js/grips/vertical.png');
}
.gutter.gutter-vertical {
cursor: row-resize;
background-image: url('../../../../node_modules/split.js/grips/horizontal.png');
}
.tb-split.tb-split-horizontal, .gutter.gutter-horizontal {
height: 100%;
float: left;
}
.tb-split.tb-split-vertical {
display: flex;
.tb-split.tb-content {
height: 100%;
}
}
div.tb-editor-area-title-panel {
position: absolute;
font-size: 0.800rem;
font-weight: 500;
top: 10px;
right: 40px;
z-index: 5;
label {
color: #00acc1;
background: rgba(220, 220, 220, 0.35);
border-radius: 5px;
padding: 4px;
text-transform: uppercase;
}
.md-button {
color: #7B7B7B;
min-width: 32px;
min-height: 15px;
line-height: 15px;
font-size: 0.800rem;
margin: 0;
padding: 4px;
background: rgba(220, 220, 220, 0.35);
}
}
.tb-resize-container {
overflow-y: auto;
height: 100%;
width: 100%;
position: relative;
.ace_editor {
height: 100%;
}
}
}

81
ui/src/app/rulechain/script/node-script-test.service.js

@ -0,0 +1,81 @@
/*
* Copyright © 2016-2018 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.
*/
/* eslint-disable import/no-unresolved, import/default */
import nodeScriptTestTemplate from './node-script-test.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function NodeScriptTest($q, $mdDialog, $document) {
var service = {
testNodeScript: testNodeScript
};
return service;
function testNodeScript($event, script, scriptType, functionTitle, functionName, argNames, msg, metadata, msgType) {
var deferred = $q.defer();
if ($event) {
$event.stopPropagation();
}
var onShowingCallback = {
onShowed: () => {
}
};
var inputParams = {
script: script,
scriptType: scriptType,
functionName: functionName,
argNames: argNames
};
$mdDialog.show({
controller: 'NodeScriptTestController',
controllerAs: 'vm',
templateUrl: nodeScriptTestTemplate,
parent: angular.element($document[0].body),
locals: {
msg: msg,
metadata: metadata,
msgType: msgType,
functionTitle: functionTitle,
inputParams: inputParams,
onShowingCallback: onShowingCallback
},
fullscreen: true,
skipHide: true,
targetEvent: $event,
onComplete: () => {
onShowingCallback.onShowed();
}
}).then(
(script) => {
deferred.resolve(script);
},
() => {
deferred.reject();
}
);
return deferred.promise;
}
}

119
ui/src/app/rulechain/script/node-script-test.tpl.html

@ -0,0 +1,119 @@
<!--
Copyright © 2016-2018 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.
-->
<md-dialog class="tb-node-script-test-dialog"
aria-label="{{ 'rulenode.test-script-function' | translate }}" style="width: 800px;">
<form flex name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2>{{ 'rulenode.test-script-function' | translate }}</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-dialog-content flex style="position: relative;">
<div class="tb-absolute-fill">
<div id="top_panel" class="tb-split tb-split-vertical">
<div id="top_left_panel" class="tb-split tb-content">
<div class="tb-resize-container">
<div class="tb-editor-area-title-panel">
<label translate>rulenode.message</label>
</div>
<ng-form name="payloadForm">
<div layout="column" style="height: 100%;">
<div layout="row">
<md-input-container class="md-block" style="margin-bottom: 0px; min-width: 200px;">
<label translate>rulenode.message-type</label>
<input required name="msgType" ng-model="vm.inputParams.msgType">
<div ng-messages="payloadForm.msgType.$error">
<div translate ng-message="required">rulenode.message-type-required</div>
</div>
</md-input-container>
</div>
<tb-json-content flex
ng-model="vm.inputParams.msg"
label="{{ 'rulenode.message' | translate }}"
content-type="vm.types.contentType.JSON.value"
validate-content="true"
validation-trigger-arg="validatePayload"
fill-height="true">
</tb-json-content>
</div>
</ng-form>
</div>
</div>
<div id="top_right_panel" class="tb-split tb-content">
<div class="tb-resize-container" id="metadata-panel">
<div class="tb-editor-area-title-panel">
<label translate>rulenode.metadata</label>
</div>
<ng-form name="metadataForm">
<tb-key-val-map title-text="rulenode.metadata" ng-disabled="$root.loading"
key-val-map="vm.inputParams.metadata"></tb-key-val-map>
</ng-form>
</div>
</div>
</div>
<div id="bottom_panel" class="tb-split tb-split-vertical">
<div id="bottom_left_panel" class="tb-split tb-content">
<div class="tb-resize-container">
<div class="tb-editor-area-title-panel">
<label>{{ vm.functionTitle }}</label>
</div>
<ng-form name="funcBodyForm">
<tb-js-func id="funcBodyInput" ng-model="vm.inputParams.script"
function-name="{{vm.inputParams.functionName}}"
function-args="{{ vm.inputParams.argNames }}"
validation-args="{{ [[vm.inputParams.msg, vm.inputParams.metadata, vm.inputParams.msgType]] }}"
validation-trigger-arg="validateFuncBody"
result-type="object"
fill-height="true">
</tb-js-func>
</ng-form>
</div>
</div>
<div id="bottom_right_panel" class="tb-split tb-content">
<div class="tb-resize-container">
<div class="tb-editor-area-title-panel">
<label translate>rulenode.output</label>
</div>
<tb-json-content ng-model="vm.output"
label="{{ 'rulenode.output' | translate }}"
content-type="vm.types.contentType.JSON.value"
validate-content="false"
ng-readonly="true"
fill-height="true">
</tb-json-content>
</div>
</div>
</div>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<md-button ng-disabled="$root.loading" ng-click="vm.test()" class="md-raised md-primary">
{{ 'rulenode.test' | translate }}
</md-button>
<span flex></span>
<md-button ng-disabled="$root.loading || theForm.funcBodyForm.$invalid || !theForm.funcBodyForm.$dirty" type="submit" class="md-raised md-primary">
{{ 'action.save' | translate }}
</md-button>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
</md-dialog-actions>
</form>
</md-dialog>
Loading…
Cancel
Save