Commit c9717f4d1ddee68a572471202f08c5e5250d81d8

Authored by Igor Kulikov
1 parent 9c0f6e99

Enrichment Rule nodes config UI

  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.data;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.relation.EntitySearchDirection;
  20 +import org.thingsboard.server.common.data.relation.EntityTypeFilter;
  21 +
  22 +import java.util.List;
  23 +
  24 +@Data
  25 +public class RelationsQuery {
  26 +
  27 + private EntitySearchDirection direction;
  28 + private int maxLevel = 1;
  29 + private List<EntityTypeFilter> filters;
  30 +
  31 +}
... ...
... ... @@ -43,7 +43,9 @@ import static org.thingsboard.server.common.data.DataConstants.*;
43 43 nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " +
44 44 "with specific prefix: <i>cs/shared/ss</i>. To access those attributes in other nodes this template can be used " +
45 45 "<code>metadata.cs.temperature</code> or <code>metadata.shared.limit</code> " +
46   - "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.")
  46 + "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.",
  47 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  48 + configDirective = "tbEnrichmentNodeOriginatorAttributesConfig")
47 49 public class TbGetAttributesNode implements TbNode {
48 50
49 51 private TbGetAttributesNodeConfiguration config;
... ...
... ... @@ -30,7 +30,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
30 30 nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata",
31 31 nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
32 32 "To access those attributes in other nodes this template can be used " +
33   - "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
  33 + "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata",
  34 + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
  35 + configDirective = "tbEnrichmentNodeCustomerAttributesConfig")
34 36 public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> {
35 37
36 38 @Override
... ...
... ... @@ -16,18 +16,19 @@
16 16 package org.thingsboard.rule.engine.metadata;
17 17
18 18 import lombok.Data;
19   -import org.thingsboard.rule.engine.api.NodeConfiguration;
  19 +import org.thingsboard.rule.engine.data.RelationsQuery;
20 20 import org.thingsboard.server.common.data.relation.EntityRelation;
21 21 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
  22 +import org.thingsboard.server.common.data.relation.EntityTypeFilter;
22 23
  24 +import java.util.Collections;
23 25 import java.util.HashMap;
24 26 import java.util.Map;
25 27
26 28 @Data
27 29 public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration {
28 30
29   - private String relationType;
30   - private EntitySearchDirection direction;
  31 + private RelationsQuery relationsQuery;
31 32
32 33 @Override
33 34 public TbGetRelatedAttrNodeConfiguration defaultConfiguration() {
... ... @@ -36,8 +37,14 @@ public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfig
36 37 attrMapping.putIfAbsent("temperature", "tempo");
37 38 configuration.setAttrMapping(attrMapping);
38 39 configuration.setTelemetry(true);
39   - configuration.setRelationType(EntityRelation.CONTAINS_TYPE);
40   - configuration.setDirection(EntitySearchDirection.FROM);
  40 +
  41 + RelationsQuery relationsQuery = new RelationsQuery();
  42 + relationsQuery.setDirection(EntitySearchDirection.FROM);
  43 + relationsQuery.setMaxLevel(1);
  44 + EntityTypeFilter entityTypeFilter = new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList());
  45 + relationsQuery.setFilters(Collections.singletonList(entityTypeFilter));
  46 + configuration.setRelationsQuery(relationsQuery);
  47 +
41 48 return configuration;
42 49 }
43 50 }
... ...
... ... @@ -32,7 +32,10 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
32 32 "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " +
33 33 "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
34 34 "To access those attributes in other nodes this template can be used " +
35   - "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
  35 + "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata",
  36 + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
  37 + configDirective = "tbEnrichmentNodeRelatedAttributesConfig")
  38 +
36 39 public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> {
37 40
38 41 private TbGetRelatedAttrNodeConfiguration config;
... ... @@ -45,6 +48,6 @@ public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> {
45 48
46 49 @Override
47 50 protected ListenableFuture<EntityId> findEntityAsync(TbContext ctx, EntityId originator) {
48   - return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, originator, config.getDirection(), config.getRelationType());
  51 + return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, originator, config.getRelationsQuery());
49 52 }
50 53 }
... ...
... ... @@ -32,7 +32,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
32 32 nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata",
33 33 nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
34 34 "To access those attributes in other nodes this template can be used " +
35   - "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
  35 + "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata",
  36 + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
  37 + configDirective = "tbEnrichmentNodeTenantAttributesConfig")
36 38 public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> {
37 39
38 40 @Override
... ...
... ... @@ -68,7 +68,7 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode {
68 68 case TENANT_SOURCE:
69 69 return EntitiesTenantIdAsyncLoader.findEntityIdAsync(ctx, original);
70 70 case RELATED_SOURCE:
71   - return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, original, config.getDirection(), config.getRelationType());
  71 + return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, original, config.getRelationsQuery());
72 72 default:
73 73 return Futures.immediateFailedFuture(new IllegalStateException("Unexpected originator source " + config.getOriginatorSource()));
74 74 }
... ... @@ -82,9 +82,9 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode {
82 82 }
83 83
84 84 if (conf.getOriginatorSource().equals(RELATED_SOURCE)) {
85   - if (conf.getDirection() == null || StringUtils.isBlank(conf.getRelationType())) {
86   - log.error("Related source for TbChangeOriginatorNode should have direction and relationType. Actual [{}] [{}]",
87   - conf.getDirection(), conf.getRelationType());
  85 + if (conf.getRelationsQuery() == null) {
  86 + log.error("Related source for TbChangeOriginatorNode should have relations query. Actual [{}]",
  87 + conf.getRelationsQuery());
88 88 throw new IllegalArgumentException("Wrong config for RElated Source in TbChangeOriginatorNode" + conf.getOriginatorSource());
89 89 }
90 90 }
... ...
... ... @@ -17,22 +17,32 @@ package org.thingsboard.rule.engine.transform;
17 17
18 18 import lombok.Data;
19 19 import org.thingsboard.rule.engine.api.NodeConfiguration;
  20 +import org.thingsboard.rule.engine.data.RelationsQuery;
20 21 import org.thingsboard.server.common.data.relation.EntityRelation;
21 22 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
  23 +import org.thingsboard.server.common.data.relation.EntityTypeFilter;
  24 +
  25 +import java.util.Collections;
22 26
23 27 @Data
24 28 public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration {
25 29
26 30 private String originatorSource;
27   - private EntitySearchDirection direction;
28   - private String relationType;
  31 +
  32 + private RelationsQuery relationsQuery;
29 33
30 34 @Override
31 35 public TbChangeOriginatorNodeConfiguration defaultConfiguration() {
32 36 TbChangeOriginatorNodeConfiguration configuration = new TbChangeOriginatorNodeConfiguration();
33 37 configuration.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE);
34   - configuration.setDirection(EntitySearchDirection.FROM);
35   - configuration.setRelationType(EntityRelation.CONTAINS_TYPE);
  38 +
  39 + RelationsQuery relationsQuery = new RelationsQuery();
  40 + relationsQuery.setDirection(EntitySearchDirection.FROM);
  41 + relationsQuery.setMaxLevel(1);
  42 + EntityTypeFilter entityTypeFilter = new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList());
  43 + relationsQuery.setFilters(Collections.singletonList(entityTypeFilter));
  44 + configuration.setRelationsQuery(relationsQuery);
  45 +
36 46 configuration.setStartNewChain(false);
37 47 return configuration;
38 48 }
... ...
... ... @@ -20,32 +20,41 @@ import com.google.common.util.concurrent.Futures;
20 20 import com.google.common.util.concurrent.ListenableFuture;
21 21 import org.apache.commons.collections.CollectionUtils;
22 22 import org.thingsboard.rule.engine.api.TbContext;
  23 +import org.thingsboard.rule.engine.data.RelationsQuery;
23 24 import org.thingsboard.server.common.data.id.EntityId;
24 25 import org.thingsboard.server.common.data.relation.EntityRelation;
  26 +import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
25 27 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
  28 +import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
26 29 import org.thingsboard.server.dao.relation.RelationService;
27 30
28 31 import java.util.List;
29 32
30   -import static org.thingsboard.server.common.data.relation.RelationTypeGroup.COMMON;
31   -
32 33 public class EntitiesRelatedEntityIdAsyncLoader {
33 34
34 35 public static ListenableFuture<EntityId> findEntityAsync(TbContext ctx, EntityId originator,
35   - EntitySearchDirection direction, String relationType) {
  36 + RelationsQuery relationsQuery) {
36 37 RelationService relationService = ctx.getRelationService();
37   - if (direction == EntitySearchDirection.FROM) {
38   - ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByFromAndTypeAsync(originator, relationType, COMMON);
  38 + EntityRelationsQuery query = buildQuery(originator, relationsQuery);
  39 + ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByQuery(query);
  40 + if (relationsQuery.getDirection() == EntitySearchDirection.FROM) {
39 41 return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>)
40 42 r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo())
41 43 : Futures.immediateFailedFuture(new IllegalStateException("Relation not found")));
42   - } else if (direction == EntitySearchDirection.TO) {
43   - ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByToAndTypeAsync(originator, relationType, COMMON);
  44 + } else if (relationsQuery.getDirection() == EntitySearchDirection.TO) {
44 45 return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>)
45 46 r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom())
46 47 : Futures.immediateFailedFuture(new IllegalStateException("Relation not found")));
47 48 }
48   -
49 49 return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction"));
50 50 }
  51 +
  52 + private static EntityRelationsQuery buildQuery(EntityId originator, RelationsQuery relationsQuery) {
  53 + EntityRelationsQuery query = new EntityRelationsQuery();
  54 + RelationsSearchParameters parameters = new RelationsSearchParameters(originator,
  55 + relationsQuery.getDirection(), relationsQuery.getMaxLevel());
  56 + query.setParameters(parameters);
  57 + query.setFilters(relationsQuery.getFilters());
  58 + return query;
  59 + }
51 60 }
... ...
1   -.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important}
  1 +.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important}.tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}.tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}.tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}.tb-kv-map-config .body .row{padding-top:5px;max-height:40px}.tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}.tb-kv-map-config .body md-input-container.cell{margin:0;max-height:40px}.tb-kv-map-config .body .md-button{margin:0}
2 2 /*# sourceMappingURL=rulenode-core-config.css.map*/
\ No newline at end of file
... ...
1   -!function(e){function t(s){if(a[s])return a[s].exports;var n=a[s]={exports:{},id:s,loaded:!1};return e[s].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var a={};return t.m=e,t.c=a,t.p="/static/",t(0)}([function(e,t,a){e.exports=a(8)},function(e,t){},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:&apos;...&apos;}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e,t,a){var s=function(s,n,r,l){function u(){if(l.$viewValue){for(var e=[],t=0;t<s.messageTypes.length;t++)e.push(s.messageTypes[t].value);l.$viewValue.messageTypes=e,o()}}function o(){if(s.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var c=i.default;n.html(c),s.selectedMessageType=null,s.messageTypeSearchText=null,s.ngModelCtrl=l;var d=[];for(var p in a.messageType){var m={name:a.messageType[p].name,value:a.messageType[p].value};d.push(m)}s.transformMessageTypeChip=function(e){var a,s=t("filter")(d,{name:e},!0);return a=s&&s.length?angular.copy(s[0]):{name:e,value:e}},s.messageTypesSearch=function(e){var a=e?t("filter")(d,{name:e}):d;return a.map(function(e){return e.name})},s.createMessageType=function(e,t){var a=angular.element(t,n)[0].firstElementChild,s=angular.element(a),r=s.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),s.scope().$mdChipsCtrl.appendChip(r.trim()),s.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var n=0;n<e.messageTypes.length;n++){var r=e.messageTypes[n];a.messageType[r]?t.push(angular.copy(a.messageType[r])):t.push({name:r,value:r})}s.messageTypes=t,s.$watch("messageTypes",function(e,t){angular.equals(e,t)||u()},!0)},e(n.contents())(s)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:s}}n.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n,a(1);var r=a(2),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,s,n){var r=i.default;a.html(r),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(3),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,s,n){var r=i.default;a.html(r),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(4),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var n=a(11),r=s(n),i=a(6),l=s(i),u=a(5),o=s(u),c=a(7),d=s(c),p=a(10),m=s(p);t.default=angular.module("thingsboard.ruleChain.config",[r.default]).directive("tbFilterNodeScriptConfig",l.default).directive("tbFilterNodeMessageTypeConfig",o.default).directive("tbFilterNodeSwitchConfig",d.default).config(m.default).name},function(e,t){"use strict";function a(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required."}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=a},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e,t){(0,i.default)(t);for(var a in t){var s=t[a];e.translations(a,s)}}n.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(9),i=s(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES:{name:"Post attributes",value:"POST_ATTRIBUTES"},POST_TELEMETRY:{name:"Post telemetry",value:"POST_TELEMETRY"},RPC_REQUEST:{name:"RPC Request",value:"RPC_REQUEST"}}}).name}]);
  1 +!function(e){function t(r){if(a[r])return a[r].exports;var n=a[r]={exports:{},id:r,loaded:!1};return e[r].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var a={};return t.m=e,t.c=a,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var a=t.slice(1),r=e[t[0]];return function(e,t,n){r.apply(this,[e,t,n].concat(a))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,a){e.exports=a(23)},function(e,t){},1,function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},3,function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:&apos;...&apos;}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px>&nbsp</span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div flex layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,r,n){var i=l.default;a.html(i),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var i=a(3),l=r(i)},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var n=a(14),i=r(n),l=a(15),s=r(l),o=a(12),u=r(o),d=a(16),c=r(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",s.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e,t){var a=function(a,r,n,i){var s=l.default;r.html(s);var o=186;a.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,o],a.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(a.configuration)}),i.$render=function(){a.configuration=i.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}n.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var i=a(4),l=r(i)},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,r,n){var i=l.default;a.html(i),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var i=a(5),l=r(i)},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,r,n){var i=l.default;a.html(i),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var i=a(6),l=r(i)},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var n=a(19),i=r(n),l=a(18),s=r(l),o=a(20),u=r(o);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",s.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e,t,a){var r=function(r,n,i,s){function o(){if(s.$viewValue){for(var e=[],t=0;t<r.messageTypes.length;t++)e.push(r.messageTypes[t].value);s.$viewValue.messageTypes=e,u()}}function u(){if(r.required){var e=!(!s.$viewValue.messageTypes||!s.$viewValue.messageTypes.length);s.$setValidity("messageTypes",e)}else s.$setValidity("messageTypes",!0)}var d=l.default;n.html(d),r.selectedMessageType=null,r.messageTypeSearchText=null,r.ngModelCtrl=s;var c=[];for(var m in a.messageType){var f={name:a.messageType[m].name,value:a.messageType[m].value};c.push(f)}r.transformMessageTypeChip=function(e){var a,r=t("filter")(c,{name:e},!0);return a=r&&r.length?angular.copy(r[0]):{name:e,value:e}},r.messageTypesSearch=function(e){var a=e?t("filter")(c,{name:e}):c;return a.map(function(e){return e.name})},r.createMessageType=function(e,t){var a=angular.element(t,n)[0].firstElementChild,r=angular.element(a),i=r.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),r.scope().$mdChipsCtrl.appendChip(i.trim()),r.scope().$mdChipsCtrl.resetChipBuffer()},s.$render=function(){var e=s.$viewValue,t=[];if(e&&e.messageTypes)for(var n=0;n<e.messageTypes.length;n++){var i=e.messageTypes[n];a.messageType[i]?t.push(angular.copy(a.messageType[i])):t.push({name:i,value:i})}r.messageTypes=t,r.$watch("messageTypes",function(e,t){angular.equals(e,t)||o()},!0)},e(n.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:r}}n.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n,a(1);var i=a(7),l=r(i)},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,r,n){var i=l.default;a.html(i),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var i=a(8),l=r(i)},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,r,n){var i=l.default;a.html(i),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var i=a(9),l=r(i)},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,r,n){function i(e){e>-1&&t.kvList.splice(e,1)}function s(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function o(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),n.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),n.$setValidity("kvMap",e)}var d=l.default;a.html(d),t.ngModelCtrl=n,t.removeKeyVal=i,t.addKeyVal=s,t.kvList=[],t.$watch("query",function(e,a){angular.equals(e,a)||n.$setViewValue(t.query)}),n.$render=function(){if(n.$viewValue){var e=n.$viewValue;t.kvList.length=0;for(var a in e)t.kvList.push({key:a,value:e[a]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||o()},!0),u()},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var i=a(10),l=r(i);a(2)},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e,t){var a=function(a,r,n,i){var s=l.default;r.html(s),a.types=t,a.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(a.query)}),i.$render=function(){a.query=i.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}n.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var i=a(11),l=r(i)},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var n=a(26),i=r(n),l=a(17),s=r(l),o=a(13),u=r(o),d=a(22),c=r(d),m=a(21),f=r(m),p=a(25),g=r(p);t.default=angular.module("thingsboard.ruleChain.config",[i.default,s.default,u.default]).directive("tbRelationsQueryConfig",c.default).directive("tbKvMapConfig",f.default).config(g.default).name},function(e,t){"use strict";function a(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified."},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=a},function(e,t,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e,t){(0,l.default)(t);for(var a in t){var r=t[a];e.translations(a,r)}}n.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var i=a(24),l=r(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES:{name:"Post attributes",value:"POST_ATTRIBUTES"},POST_TELEMETRY:{name:"Post telemetry",value:"POST_TELEMETRY"},RPC_REQUEST:{name:"RPC Request",value:"RPC_REQUEST"}}}).name}]));
2 2 //# sourceMappingURL=rulenode-core-config.js.map
\ No newline at end of file
... ...
... ... @@ -46,6 +46,7 @@ export default function RelationFilters($compile, $templateCache) {
46 46 ngModelCtrl.$render = function () {
47 47 if (ngModelCtrl.$viewValue) {
48 48 var value = ngModelCtrl.$viewValue;
  49 + scope.relationFilters.length = 0;
49 50 value.forEach(function (filter) {
50 51 scope.relationFilters.push(filter);
51 52 });
... ...