Multiple labels support.

This commit is contained in:
Igor Kulikov 2018-06-18 14:33:50 +03:00
parent 1f58c1b0d6
commit f081b656d0
13 changed files with 318 additions and 53 deletions

View File

@ -32,6 +32,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
getRuleNodeComponents: getRuleNodeComponents,
getRuleNodeComponentByClazz: getRuleNodeComponentByClazz,
getRuleNodeSupportedLinks: getRuleNodeSupportedLinks,
ruleNodeAllowCustomLinks: ruleNodeAllowCustomLinks,
resolveTargetRuleChains: resolveTargetRuleChains,
testScript: testScript,
getLatestRuleNodeDebugInput: getLatestRuleNodeDebugInput
@ -127,21 +128,21 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
function getRuleNodeSupportedLinks(component) {
var relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes;
var customRelations = component.configurationDescriptor.nodeDefinition.customRelations;
var linkLabels = [];
var linkLabels = {};
for (var i=0;i<relationTypes.length;i++) {
linkLabels.push({
name: relationTypes[i], custom: false
});
}
if (customRelations) {
linkLabels.push(
{ name: 'Custom', custom: true }
);
var label = relationTypes[i];
linkLabels[label] = {
name: label,
value: label
};
}
return linkLabels;
}
function ruleNodeAllowCustomLinks(component) {
return component.configurationDescriptor.nodeDefinition.customRelations;
}
function getRuleNodeComponents() {
var deferred = $q.defer();
if (ruleNodeComponents) {
@ -226,7 +227,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
if (res && res.length) {
return res[0];
}
return null;
var unknownComponent = angular.copy(types.unknownNodeComponent);
unknownComponent.clazz = clazz;
unknownComponent.configurationDescriptor.nodeDefinition.details = "Unknown Rule Node class: " + clazz;
return unknownComponent;
}
function resolveTargetRuleChains(ruleChainConnections) {

View File

@ -510,6 +510,22 @@ export default angular.module('thingsboard.types', [])
}
}
},
unknownNodeComponent: {
type: 'UNKNOWN',
name: 'unknown',
clazz: 'tb.internal.Unknown',
configurationDescriptor: {
nodeDefinition: {
description: "",
details: "",
inEnabled: true,
outEnabled: true,
relationTypes: [],
customRelations: false,
defaultConfiguration: {}
}
}
},
inputNodeComponent: {
type: 'INPUT',
name: 'Input',
@ -565,6 +581,13 @@ export default angular.module('thingsboard.types', [])
nodeClass: "tb-input-type",
icon: "input",
special: true
},
UNKNOWN: {
value: "UNKNOWN",
name: "rulenode.type-unknown",
details: "rulenode.type-unknown-details",
nodeClass: "tb-unknown-type",
icon: "help_outline"
}
},
valueType: {

View File

@ -16,8 +16,8 @@
-->
<div flex layout="column" style="margin-top: -10px;">
<div flex>{{vm.item.additionalInfo.description}}</div>
<div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
<div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
<div style="text-transform: uppercase; padding-bottom: 5px;">{{vm.item.type}}</div>
<div class="tb-card-description">{{vm.item.additionalInfo.description}}</div>
<div style="padding-top: 5px;" class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
<div style="padding-top: 5px;" class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
</div>

View File

@ -1157,6 +1157,11 @@ export default angular.module('thingsboard.locale', [])
"link-label-required": "Link label is required.",
"custom-link-label": "Custom link label",
"custom-link-label-required": "Custom link label is required.",
"link-labels": "Link labels",
"link-labels-required": "Link labels is required.",
"no-link-labels-found": "No link labels found",
"no-link-label-matching": "'{{label}}' not found.",
"create-new-link-label": "Create a new one!",
"type-filter": "Filter",
"type-filter-details": "Filter incoming messages with configured conditions",
"type-enrichment": "Enrichment",
@ -1171,6 +1176,8 @@ export default angular.module('thingsboard.locale', [])
"type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
"type-input": "Input",
"type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node",
"type-unknown": "Unknown",
"type-unknown-details": "Unresolved Rule Node",
"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!",

View File

@ -31,7 +31,7 @@
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<tb-rule-node-link link="vm.link" labels="vm.labels" is-edit="true" the-form="theForm"></tb-rule-node-link>
<tb-rule-node-link ng-model="vm.link" allowed-labels="vm.labels" is-edit="true" allow-custom="vm.allowCustomLabels"></tb-rule-node-link>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">

View File

@ -17,7 +17,46 @@
-->
<md-content class="md-padding tb-link" layout="column">
<fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
<md-input-container class="md-block">
<label translate class="tb-title no-padding" ng-class="{'tb-required': required}">rulenode.link-labels</label>
<md-chips id="link_label_chips"
ng-required="true"
readonly="$root.loading || !isEdit || isReadOnly"
ng-model="labels" md-autocomplete-snap
md-transform-chip="transformLinkLabelChip($chip)"
md-require-match="!allowCustom">
<md-autocomplete
id="link_label"
md-no-cache="true"
md-selected-item="selectedLabel"
md-search-text="labelSearchText"
md-items="item in labelsSearch(labelSearchText)"
md-item-text="item.name"
md-min-length="0"
placeholder="{{'rulenode.link-label' | translate }}"
md-menu-class="tb-link-label-autocomplete">
<span md-highlight-text="labelSearchText" md-highlight-flags="^i">{{item}}</span>
<md-not-found>
<div class="tb-not-found">
<div class="tb-no-entries" ng-if="!labelSearchText || !labelSearchText.length">
<span translate>rulenode.no-link-labels-found</span>
</div>
<div ng-if="labelSearchText && labelSearchText.length">
<span translate translate-values='{ label: "{{labelSearchText | truncate:true:6:&apos;...&apos;}}" }'>rulenode.no-link-label-matching</span>
<span ng-if="allowCustom">
<a translate ng-click="createLinkLabel($event, '#link_label_chips')">rulenode.create-new-link-label</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="linkLabels" class="tb-error-message">rulenode.link-labels-required</div>
</div>
<!--md-input-container class="md-block">
<label translate>rulenode.link-label</label>
<md-select ng-model="selectedLabel" ng-change="selectedLabelChanged()">
<md-option ng-repeat="label in labels" ng-value="label">
@ -34,6 +73,6 @@
<div ng-messages="theForm.customLinkLabel.$error">
<div translate ng-message="required">rulenode.custom-link-label-required</div>
</div>
</md-input-container>
</md-input-container-->
</fieldset>
</md-content>

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
import './link.scss';
/* eslint-disable import/no-unresolved, import/default */
import linkFieldsetTemplate from './link-fieldset.tpl.html';
@ -22,13 +24,18 @@ import linkFieldsetTemplate from './link-fieldset.tpl.html';
/*@ngInject*/
export default function LinkDirective($compile, $templateCache, $filter) {
var linker = function (scope, element) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(linkFieldsetTemplate);
element.html(template);
scope.selectedLabel = null;
scope.labelSearchText = null;
scope.$watch('link', function() {
scope.ngModelCtrl = ngModelCtrl;
var labelsList = [];
/*scope.$watch('link', function() {
scope.selectedLabel = null;
if (scope.link && scope.labels) {
if (scope.link.label) {
@ -53,19 +60,100 @@ export default function LinkDirective($compile, $templateCache, $filter) {
scope.link.label = "";
}
}
};*/
scope.transformLinkLabelChip = function (chip) {
var res = $filter('filter')(labelsList, {name: chip}, true);
var result;
if (res && res.length) {
result = angular.copy(res[0]);
} else {
result = {
name: chip,
value: chip
};
}
return result;
};
scope.labelsSearch = function (searchText) {
var labels = searchText ? $filter('filter')(labelsList, {name: searchText}) : labelsList;
return labels.map((label) => label.name);
};
scope.createLinkLabel = function (event, chipsId) {
var chipsChild = angular.element(chipsId, element)[0].firstElementChild;
var el = angular.element(chipsChild);
var chipBuffer = el.scope().$mdChipsCtrl.getChipBuffer();
event.preventDefault();
event.stopPropagation();
el.scope().$mdChipsCtrl.appendChip(chipBuffer.trim());
el.scope().$mdChipsCtrl.resetChipBuffer();
};
ngModelCtrl.$render = function () {
labelsList.length = 0;
for (var label in scope.allowedLabels) {
var linkLabel = {
name: scope.allowedLabels[label].name,
value: scope.allowedLabels[label].value
};
labelsList.push(linkLabel);
}
var link = ngModelCtrl.$viewValue;
var labels = [];
if (link && link.labels) {
for (var i = 0; i < link.labels.length; i++) {
label = link.labels[i];
if (scope.allowedLabels[label]) {
labels.push(angular.copy(scope.allowedLabels[label]));
} else {
labels.push({
name: label,
value: label
});
}
}
}
scope.labels = labels;
scope.$watch('labels', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
updateLabels();
}
}, true);
};
function updateLabels() {
if (ngModelCtrl.$viewValue) {
var labels = [];
for (var i = 0; i < scope.labels.length; i++) {
labels.push(scope.labels[i].value);
}
ngModelCtrl.$viewValue.labels = labels;
ngModelCtrl.$viewValue.label = labels.join(' / ');
updateValidity();
}
}
function updateValidity() {
var valid = ngModelCtrl.$viewValue.labels &&
ngModelCtrl.$viewValue.labels.length ? true : false;
ngModelCtrl.$setValidity('linkLabels', valid);
}
$compile(element.contents())(scope);
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
link: '=',
labels: '=',
allowedLabels: '=',
allowCustom: '=',
isEdit: '=',
isReadOnly: '=',
theForm: '='
isReadOnly: '='
}
};
}

View File

@ -0,0 +1,30 @@
/**
* 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-link-label-autocomplete {
.tb-not-found {
display: block;
line-height: 1.5;
height: 48px;
.tb-no-entries {
line-height: 48px;
}
}
li {
height: auto !important;
white-space: normal !important;
}
}

View File

@ -669,11 +669,15 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
}
} else {
if (edge.label) {
if (!edge.labels) {
edge.labels = edge.label.split(' / ');
}
deferred.resolve(edge);
} else {
var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
var allowCustomLabels = ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
vm.enableHotKeys = false;
addRuleNodeLink(event, edge, labels).then(
addRuleNodeLink(event, edge, labels, allowCustomLabels).then(
(link) => {
deferred.resolve(link);
vm.enableHotKeys = true;
@ -713,6 +717,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
vm.isEditingRuleNode = false;
vm.editingRuleNode = null;
vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
vm.editingRuleNodeAllowCustomLabels = ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
vm.isEditingRuleNodeLink = true;
vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
vm.editingRuleNodeLink = angular.copy(edge);
@ -744,7 +749,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
isInputSource: isInputSource,
fromIndex: fromIndex,
toIndex: toIndex,
label: edge.label
label: edge.label,
labels: edge.labels
};
connections.push(connection);
}
@ -816,7 +822,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var edge = {
source: source,
destination: destination,
label: connection.label
label: connection.label,
labels: connection.labels
};
vm.ruleChainModel.edges.push(edge);
vm.modelservice.edges.select(edge);
@ -1024,6 +1031,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
}
if (vm.ruleChainMetaData.connections) {
var edgeMap = {};
for (i = 0; i < vm.ruleChainMetaData.connections.length; i++) {
var connection = vm.ruleChainMetaData.connections[i];
var sourceNode = nodes[connection.fromIndex];
@ -1032,12 +1040,23 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var sourceConnectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
var destConnectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType);
if (sourceConnectors && sourceConnectors.length && destConnectors && destConnectors.length) {
edge = {
source: sourceConnectors[0].id,
destination: destConnectors[0].id,
label: connection.type
};
vm.ruleChainModel.edges.push(edge);
var sourceId = sourceConnectors[0].id;
var destId = destConnectors[0].id;
var edgeKey = sourceId + '_' + destId;
edge = edgeMap[edgeKey];
if (!edge) {
edge = {
source: sourceId,
destination: destId,
label: connection.type,
labels: [connection.type]
};
edgeMap[edgeKey] = edge;
vm.ruleChainModel.edges.push(edge);
} else {
edge.label += ' / ' +connection.type;
edge.labels.push(connection.type);
}
}
}
}
@ -1045,6 +1064,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
if (vm.ruleChainMetaData.ruleChainConnections) {
var ruleChainNodesMap = {};
var ruleChainEdgeMap = {};
for (i = 0; i < vm.ruleChainMetaData.ruleChainConnections.length; i++) {
var ruleChainConnection = vm.ruleChainMetaData.ruleChainConnections[i];
var ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id];
@ -1081,12 +1101,23 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
if (sourceNode) {
connectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
if (connectors && connectors.length) {
var ruleChainEdge = {
source: connectors[0].id,
destination: ruleChainNode.connectors[0].id,
label: ruleChainConnection.type
};
vm.ruleChainModel.edges.push(ruleChainEdge);
sourceId = connectors[0].id;
destId = ruleChainNode.connectors[0].id;
edgeKey = sourceId + '_' + destId;
var ruleChainEdge = ruleChainEdgeMap[edgeKey];
if (!ruleChainEdge) {
ruleChainEdge = {
source: sourceId,
destination: destId,
label: ruleChainConnection.type,
labels: [ruleChainConnection.type]
};
ruleChainEdgeMap[edgeKey] = ruleChainEdge;
vm.ruleChainModel.edges.push(ruleChainEdge);
} else {
ruleChainEdge.label += ' / ' +ruleChainConnection.type;
ruleChainEdge.labels.push(ruleChainConnection.type);
}
}
}
}
@ -1199,8 +1230,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var ruleChainConnection = {
fromIndex: fromIndex,
targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId},
additionalInfo: destNode.additionalInfo,
type: edge.label
additionalInfo: destNode.additionalInfo
};
if (!ruleChainConnection.additionalInfo) {
ruleChainConnection.additionalInfo = {};
@ -1208,15 +1238,22 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
ruleChainConnection.additionalInfo.layoutX = Math.round(destNode.x);
ruleChainConnection.additionalInfo.layoutY = Math.round(destNode.y);
ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id;
ruleChainMetaData.ruleChainConnections.push(ruleChainConnection);
for (var rcIndex=0;rcIndex<edge.labels.length;rcIndex++) {
var newRuleChainConnection = angular.copy(ruleChainConnection);
newRuleChainConnection.type = edge.labels[rcIndex];
ruleChainMetaData.ruleChainConnections.push(newRuleChainConnection);
}
} else {
var toIndex = nodes.indexOf(destNode);
var nodeConnection = {
fromIndex: fromIndex,
toIndex: toIndex,
type: edge.label
toIndex: toIndex
};
ruleChainMetaData.connections.push(nodeConnection);
for (var cIndex=0;cIndex<edge.labels.length;cIndex++) {
var newNodeConnection = angular.copy(nodeConnection);
newNodeConnection.type = edge.labels[cIndex];
ruleChainMetaData.connections.push(newNodeConnection);
}
}
}
}
@ -1285,13 +1322,13 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
});
}
function addRuleNodeLink($event, link, labels) {
function addRuleNodeLink($event, link, labels, allowCustomLabels) {
return $mdDialog.show({
controller: 'AddRuleNodeLinkController',
controllerAs: 'vm',
templateUrl: addRuleNodeLinkTemplate,
parent: angular.element($document[0].body),
locals: {link: link, labels: labels},
locals: {link: link, labels: labels, allowCustomLabels: allowCustomLabels},
fullscreen: true,
targetEvent: $event
});
@ -1335,13 +1372,14 @@ export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId,
}
/*@ngInject*/
export function AddRuleNodeLinkController($scope, $mdDialog, link, labels, helpLinks) {
export function AddRuleNodeLinkController($scope, $mdDialog, link, labels, allowCustomLabels, helpLinks) {
var vm = this;
vm.helpLinks = helpLinks;
vm.link = link;
vm.labels = labels;
vm.allowCustomLabels = allowCustomLabels;
vm.add = add;
vm.cancel = cancel;

View File

@ -170,6 +170,9 @@
&.tb-rule-chain-type {
background-color: #d6c4f1;
}
&.tb-unknown-type {
background-color: #f16c29;
}
}
.tb-rule-node {
@ -202,6 +205,7 @@
background-color: #a3eaa9;
user-select: none;
}
md-icon {
font-size: 20px;
width: 20px;

View File

@ -207,11 +207,11 @@
</details-buttons>
<form name="vm.ruleNodeLinkForm" ng-if="vm.isEditingRuleNodeLink">
<tb-rule-node-link
link="vm.editingRuleNodeLink"
labels="vm.editingRuleNodeLinkLabels"
ng-model="vm.editingRuleNodeLink"
allowed-labels="vm.editingRuleNodeLinkLabels"
allow-custom="vm.editingRuleNodeAllowCustomLabels"
is-edit="true"
is-read-only="false"
the-form="vm.ruleNodeLinkForm">
is-read-only="false">
</tb-rule-node-link>
</form>
</tb-details-sidenav>

View File

@ -16,6 +16,7 @@
@import "~compass-sass-mixins/lib/compass";
@import "constants";
@import "animations";
@import "mixins";
@import "fonts";
/***************
@ -437,6 +438,12 @@ pre.tb-highlight {
}
}
.tb-card-description {
color: rgba(0,0,0,0.54);
font-size: 13px;
@include line-clamp(2, 1.1);
}
/***********************
* Flow
***********************/

View File

@ -31,4 +31,29 @@
&:-ms-input-placeholder {
@content;
}
}
}
@mixin line-clamp($numLines: 1, $lineHeight: 1.412) {
overflow: hidden;
position: relative;
line-height: $lineHeight;
text-align: justify;
margin-right: -1em;
padding-right: 2em;
max-height: ($numLines*$lineHeight)+em;
&:before {
content: '...';
position: absolute;
right: 1em;
bottom: 0;
}
&:after {
content: '';
position: absolute;
right: 1em;
width: 1em;
height: 1em;
margin-top: 0.2em;
background: white;
}
}