Browse Source

Introduce Flow rule node type. Add rile node UIs for RuleChain input/output. Remove rule chain connection metadata.

pull/5673/head
Igor Kulikov 5 years ago
parent
commit
d6e18d05cd
  1. 27
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  2. 2
      common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java
  3. 1
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java
  4. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
  5. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java
  6. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java
  7. 8
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java
  8. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java
  9. 96
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  10. 83
      ui-ngx/src/app/core/http/rule-chain.service.ts
  11. 9
      ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts
  12. 8
      ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts
  13. 3
      ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html
  14. 74
      ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts
  15. 2
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss
  16. 9
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts
  17. 20
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html
  18. 62
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts
  19. 1
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html
  20. 3
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts
  21. 3
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html
  22. 2
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss
  23. 147
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
  24. 12
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts
  25. 4
      ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html
  26. 43
      ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts
  27. 3
      ui-ngx/src/app/shared/models/component-descriptor.models.ts
  28. 33
      ui-ngx/src/app/shared/models/rule-chain.models.ts
  29. 23
      ui-ngx/src/app/shared/models/rule-node.models.ts
  30. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

27
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -152,24 +152,14 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
try { try {
scannedComponent.setType(type); scannedComponent.setType(type);
Class<?> clazz = Class.forName(clazzName); Class<?> clazz = Class.forName(clazzName);
switch (type) { RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
case ENRICHMENT: scannedComponent.setName(ruleNodeAnnotation.name());
case FILTER: scannedComponent.setScope(ruleNodeAnnotation.scope());
case TRANSFORMATION: NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation);
case ACTION: ObjectNode configurationDescriptor = mapper.createObjectNode();
case EXTERNAL: JsonNode node = mapper.valueToTree(nodeDefinition);
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class); configurationDescriptor.set("nodeDefinition", node);
scannedComponent.setName(ruleNodeAnnotation.name()); scannedComponent.setConfigurationDescriptor(configurationDescriptor);
scannedComponent.setScope(ruleNodeAnnotation.scope());
NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation);
ObjectNode configurationDescriptor = mapper.createObjectNode();
JsonNode node = mapper.valueToTree(nodeDefinition);
configurationDescriptor.set("nodeDefinition", node);
scannedComponent.setConfigurationDescriptor(configurationDescriptor);
break;
default:
throw new RuntimeException(type + " is not supported yet!");
}
scannedComponent.setClazz(clazzName); scannedComponent.setClazz(clazzName);
log.info("Processing scanned component: {}", scannedComponent); log.info("Processing scanned component: {}", scannedComponent);
} catch (Exception e) { } catch (Exception e) {
@ -200,6 +190,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled()); nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled());
nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(nodeAnnotation)); nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(nodeAnnotation));
nodeDefinition.setCustomRelations(nodeAnnotation.customRelations()); nodeDefinition.setCustomRelations(nodeAnnotation.customRelations());
nodeDefinition.setRuleChainNode(nodeAnnotation.ruleChainNode());
Class<? extends NodeConfiguration> configClazz = nodeAnnotation.configClazz(); Class<? extends NodeConfiguration> configClazz = nodeAnnotation.configClazz();
NodeConfiguration config = configClazz.getDeclaredConstructor().newInstance(); NodeConfiguration config = configClazz.getDeclaredConstructor().newInstance();
NodeConfiguration defaultConfiguration = config.defaultConfiguration(); NodeConfiguration defaultConfiguration = config.defaultConfiguration();

2
common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java

@ -20,6 +20,6 @@ package org.thingsboard.server.common.data.plugin;
*/ */
public enum ComponentType { public enum ComponentType {
ENRICHMENT, FILTER, TRANSFORMATION, ACTION, EXTERNAL ENRICHMENT, FILTER, TRANSFORMATION, ACTION, EXTERNAL, FLOW
} }

1
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java

@ -27,6 +27,7 @@ public class NodeDefinition {
private boolean outEnabled; private boolean outEnabled;
String[] relationTypes; String[] relationTypes;
boolean customRelations; boolean customRelations;
boolean ruleChainNode;
JsonNode defaultConfiguration; JsonNode defaultConfiguration;
String[] uiResources; String[] uiResources;
String configDirective; String configDirective;

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java

@ -58,6 +58,8 @@ public @interface RuleNode {
boolean customRelations() default false; boolean customRelations() default false;
boolean ruleChainNode() default false;
RuleChainType[] ruleChainTypes() default {RuleChainType.CORE, RuleChainType.EDGE}; RuleChainType[] ruleChainTypes() default {RuleChainType.CORE, RuleChainType.EDGE};
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java

@ -28,7 +28,7 @@ import org.thingsboard.server.common.msg.TbMsg;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.FLOW,
name = "acknowledge", name = "acknowledge",
configClazz = EmptyNodeConfiguration.class, configClazz = EmptyNodeConfiguration.class,
nodeDescription = "Acknowledges the incoming message", nodeDescription = "Acknowledges the incoming message",

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java

@ -31,7 +31,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.FLOW,
name = "checkpoint", name = "checkpoint",
configClazz = TbCheckpointNodeConfiguration.class, configClazz = TbCheckpointNodeConfiguration.class,
nodeDescription = "transfers the message to another queue", nodeDescription = "transfers the message to another queue",

8
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java

@ -33,14 +33,18 @@ import java.util.UUID;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.FLOW,
name = "rule chain input", name = "rule chain",
configClazz = TbRuleChainInputNodeConfiguration.class, configClazz = TbRuleChainInputNodeConfiguration.class,
nodeDescription = "transfers the message to another rule chain", nodeDescription = "transfers the message to another rule chain",
nodeDetails = "Allows to nest the rule chain similar to single rule node. " + nodeDetails = "Allows to nest the rule chain similar to single rule node. " +
"The incoming message is forwarded to the input node of the specified target rule chain. " + "The incoming message is forwarded to the input node of the specified target rule chain. " +
"The target rule chain may produce multiple labeled outputs. " + "The target rule chain may produce multiple labeled outputs. " +
"You may use the outputs to forward the results of processing to other rule nodes.", "You may use the outputs to forward the results of processing to other rule nodes.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbFlowNodeRuleChainInputConfig",
relationTypes = {},
ruleChainNode = true,
customRelations = true customRelations = true
) )
public class TbRuleChainInputNode implements TbNode { public class TbRuleChainInputNode implements TbNode {

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java

@ -28,14 +28,16 @@ import org.thingsboard.server.common.msg.TbMsg;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.FLOW,
name = "rule chain output", name = "output",
configClazz = TbRuleChainOutputNodeConfiguration.class, configClazz = TbRuleChainOutputNodeConfiguration.class,
nodeDescription = "transfers the message to the caller rule chain", nodeDescription = "transfers the message to the caller rule chain",
nodeDetails = "Produces output of the rule chain processing. " + nodeDetails = "Produces output of the rule chain processing. " +
"The output is forwarded to the caller rule chain, as an output of the corresponding \"input\" rule node. " + "The output is forwarded to the caller rule chain, as an output of the corresponding \"input\" rule node. " +
"The rule node configuration contains the \"label\" parameter. " + "The rule node configuration contains the \"label\" parameter. " +
"This parameter corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain. ", "This parameter corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain. ",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbFlowNodeRuleChainOutputConfig",
outEnabled = false outEnabled = false
) )
public class TbRuleChainOutputNode implements TbNode { public class TbRuleChainOutputNode implements TbNode {

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

@ -3691,6 +3691,91 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImpo
}] }]
}] }); }] });
class RuleChainInputComponent extends RuleNodeConfigurationComponent {
constructor(store, fb) {
super(store);
this.store = store;
this.fb = fb;
this.entityType = EntityType;
}
configForm() {
return this.ruleChainInputConfigForm;
}
onConfigurationSet(configuration) {
this.ruleChainInputConfigForm = this.fb.group({
ruleChainId: [configuration ? configuration.ruleChainId : null, [Validators.required]]
});
}
}
RuleChainInputComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleChainInputComponent, deps: [{ token: i1.Store }, { token: i2.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
RuleChainInputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.14", type: RuleChainInputComponent, selector: "tb-flow-node-rule-chain-input-config", usesInheritance: true, ngImport: i0, template: "<section [formGroup]=\"ruleChainInputConfigForm\" fxLayout=\"column\">\n <tb-entity-autocomplete required\n [excludeEntityIds]=\"[ruleChainId]\"\n [entityType]=\"entityType.RULE_CHAIN\"\n [entitySubtype]=\"ruleChainType\"\n formControlName=\"ruleChainId\">\n </tb-entity-autocomplete>\n</section>\n", components: [{ type: i8$3.EntityAutocompleteComponent, selector: "tb-entity-autocomplete", inputs: ["entityType", "entitySubtype", "excludeEntityIds", "labelText", "requiredText", "required", "disabled"], outputs: ["entityChanged"] }], directives: [{ type: i8.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i2.FormControlName, selector: "[formControlName]", inputs: ["disabled", "formControlName", "ngModel"], outputs: ["ngModelChange"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleChainInputComponent, decorators: [{
type: Component,
args: [{
selector: 'tb-flow-node-rule-chain-input-config',
templateUrl: './rule-chain-input.component.html',
styleUrls: []
}]
}], ctorParameters: function () { return [{ type: i1.Store }, { type: i2.FormBuilder }]; } });
class RuleChainOutputComponent extends RuleNodeConfigurationComponent {
constructor(store, fb) {
super(store);
this.store = store;
this.fb = fb;
}
configForm() {
return this.ruleChainOutputConfigForm;
}
onConfigurationSet(configuration) {
this.ruleChainOutputConfigForm = this.fb.group({
label: [configuration ? configuration.label : null, [Validators.required]]
});
}
}
RuleChainOutputComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleChainOutputComponent, deps: [{ token: i1.Store }, { token: i2.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
RuleChainOutputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.14", type: RuleChainOutputComponent, selector: "tb-flow-node-rule-chain-output-config", usesInheritance: true, ngImport: i0, template: "<section [formGroup]=\"ruleChainOutputConfigForm\" fxLayout=\"column\">\n <mat-form-field class=\"mat-block\">\n <mat-label translate>tb.rulenode.label</mat-label>\n <input required matInput formControlName=\"label\">\n <mat-error *ngIf=\"ruleChainOutputConfigForm.get('label').hasError('required')\">\n {{ 'tb.rulenode.label-required' | translate }}\n </mat-error>\n </mat-form-field>\n</section>\n", components: [{ type: i3.MatFormField, selector: "mat-form-field", inputs: ["color", "floatLabel", "appearance", "hideRequiredMarker", "hintLabel"], exportAs: ["matFormField"] }], directives: [{ type: i8.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { type: i3.MatLabel, selector: "mat-label" }, { type: i4.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { type: i11.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["id", "disabled", "required", "type", "value", "readonly", "placeholder", "errorStateMatcher", "aria-describedby"], exportAs: ["matInput"] }, { type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i2.FormControlName, selector: "[formControlName]", inputs: ["disabled", "formControlName", "ngModel"], outputs: ["ngModelChange"] }, { type: i10.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.MatError, selector: "mat-error", inputs: ["id"] }], pipes: { "translate": i4.TranslatePipe } });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleChainOutputComponent, decorators: [{
type: Component,
args: [{
selector: 'tb-flow-node-rule-chain-output-config',
templateUrl: './rule-chain-output.component.html',
styleUrls: []
}]
}], ctorParameters: function () { return [{ type: i1.Store }, { type: i2.FormBuilder }]; } });
class RuleNodeCoreConfigFlowModule {
}
RuleNodeCoreConfigFlowModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigFlowModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
RuleNodeCoreConfigFlowModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigFlowModule, declarations: [RuleChainInputComponent,
RuleChainOutputComponent], imports: [CommonModule,
SharedModule,
RulenodeCoreConfigCommonModule], exports: [RuleChainInputComponent,
RuleChainOutputComponent] });
RuleNodeCoreConfigFlowModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigFlowModule, imports: [[
CommonModule,
SharedModule,
RulenodeCoreConfigCommonModule
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigFlowModule, decorators: [{
type: NgModule,
args: [{
declarations: [
RuleChainInputComponent,
RuleChainOutputComponent
],
imports: [
CommonModule,
SharedModule,
RulenodeCoreConfigCommonModule
],
exports: [
RuleChainInputComponent,
RuleChainOutputComponent
]
}]
}] });
function addRuleNodeCoreLocaleEnglish(translate) { function addRuleNodeCoreLocaleEnglish(translate) {
const enUS = { const enUS = {
tb: { tb: {
@ -4105,7 +4190,9 @@ function addRuleNodeCoreLocaleEnglish(translate) {
'for value from message body', 'for value from message body',
'alarm-severity-pattern-hint': 'Hint: use <code><span style="color: #000;">$&#123;</span>metadataKey<span style="color: #000;">&#125;</span></code> ' + 'alarm-severity-pattern-hint': 'Hint: use <code><span style="color: #000;">$&#123;</span>metadataKey<span style="color: #000;">&#125;</span></code> ' +
'for value from metadata, <code><span style="color: #000;">$[</span>messageKey<span style="color: #000;">]</span></code> ' + 'for value from metadata, <code><span style="color: #000;">$[</span>messageKey<span style="color: #000;">]</span></code> ' +
'for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)' 'for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)',
label: 'Label',
'label-required': 'Label is required'
}, },
'key-val': { 'key-val': {
key: 'Key', key: 'Key',
@ -4134,6 +4221,7 @@ RuleNodeCoreConfigModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0"
RuleNodeCoreConfigFilterModule, RuleNodeCoreConfigFilterModule,
RulenodeCoreConfigEnrichmentModule, RulenodeCoreConfigEnrichmentModule,
RulenodeCoreConfigTransformModule, RulenodeCoreConfigTransformModule,
RuleNodeCoreConfigFlowModule,
EmptyConfigComponent] }); EmptyConfigComponent] });
RuleNodeCoreConfigModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigModule, imports: [[ RuleNodeCoreConfigModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigModule, imports: [[
CommonModule, CommonModule,
@ -4141,7 +4229,8 @@ RuleNodeCoreConfigModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0"
], RuleNodeCoreConfigActionModule, ], RuleNodeCoreConfigActionModule,
RuleNodeCoreConfigFilterModule, RuleNodeCoreConfigFilterModule,
RulenodeCoreConfigEnrichmentModule, RulenodeCoreConfigEnrichmentModule,
RulenodeCoreConfigTransformModule] }); RulenodeCoreConfigTransformModule,
RuleNodeCoreConfigFlowModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigModule, decorators: [{ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigModule, decorators: [{
type: NgModule, type: NgModule,
args: [{ args: [{
@ -4157,6 +4246,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImpo
RuleNodeCoreConfigFilterModule, RuleNodeCoreConfigFilterModule,
RulenodeCoreConfigEnrichmentModule, RulenodeCoreConfigEnrichmentModule,
RulenodeCoreConfigTransformModule, RulenodeCoreConfigTransformModule,
RuleNodeCoreConfigFlowModule,
EmptyConfigComponent EmptyConfigComponent
] ]
}] }]
@ -4170,5 +4260,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImpo
* Generated bundle index. Do not edit. * Generated bundle index. Do not edit.
*/ */
export { AssignCustomerConfigComponent, AttributesConfigComponent, AzureIotHubConfigComponent, CalculateDeltaConfigComponent, ChangeOriginatorConfigComponent, CheckAlarmStatusComponent, CheckMessageConfigComponent, CheckPointConfigComponent, CheckRelationConfigComponent, ClearAlarmConfigComponent, CreateAlarmConfigComponent, CreateRelationConfigComponent, CredentialsConfigComponent, CustomerAttributesConfigComponent, DeleteRelationConfigComponent, DeviceAttributesConfigComponent, DeviceProfileConfigComponent, DeviceRelationsQueryConfigComponent, EmptyConfigComponent, EntityDetailsConfigComponent, GeneratorConfigComponent, GetTelemetryFromDatabaseConfigComponent, GpsGeoActionConfigComponent, GpsGeoFilterConfigComponent, KafkaConfigComponent, KvMapConfigComponent, LogConfigComponent, MessageTypeConfigComponent, MessageTypesConfigComponent, MqttConfigComponent, MsgCountConfigComponent, MsgDelayConfigComponent, OriginatorAttributesConfigComponent, OriginatorFieldsConfigComponent, OriginatorTypeConfigComponent, PubSubConfigComponent, PushToCloudConfigComponent, PushToEdgeConfigComponent, RabbitMqConfigComponent, RelatedAttributesConfigComponent, RelationsQueryConfigComponent, RestApiCallConfigComponent, RpcReplyConfigComponent, RpcRequestConfigComponent, RuleNodeCoreConfigActionModule, RuleNodeCoreConfigFilterModule, RuleNodeCoreConfigModule, RulenodeCoreConfigCommonModule, RulenodeCoreConfigEnrichmentModule, RulenodeCoreConfigTransformModule, SafeHtmlPipe, SaveToCustomTableConfigComponent, ScriptConfigComponent, SendEmailConfigComponent, SendSmsConfigComponent, SnsConfigComponent, SqsConfigComponent, SwitchConfigComponent, TenantAttributesConfigComponent, TimeseriesConfigComponent, ToEmailConfigComponent, TransformScriptConfigComponent, UnassignCustomerConfigComponent }; export { AssignCustomerConfigComponent, AttributesConfigComponent, AzureIotHubConfigComponent, CalculateDeltaConfigComponent, ChangeOriginatorConfigComponent, CheckAlarmStatusComponent, CheckMessageConfigComponent, CheckPointConfigComponent, CheckRelationConfigComponent, ClearAlarmConfigComponent, CreateAlarmConfigComponent, CreateRelationConfigComponent, CredentialsConfigComponent, CustomerAttributesConfigComponent, DeleteRelationConfigComponent, DeviceAttributesConfigComponent, DeviceProfileConfigComponent, DeviceRelationsQueryConfigComponent, EmptyConfigComponent, EntityDetailsConfigComponent, GeneratorConfigComponent, GetTelemetryFromDatabaseConfigComponent, GpsGeoActionConfigComponent, GpsGeoFilterConfigComponent, KafkaConfigComponent, KvMapConfigComponent, LogConfigComponent, MessageTypeConfigComponent, MessageTypesConfigComponent, MqttConfigComponent, MsgCountConfigComponent, MsgDelayConfigComponent, OriginatorAttributesConfigComponent, OriginatorFieldsConfigComponent, OriginatorTypeConfigComponent, PubSubConfigComponent, PushToCloudConfigComponent, PushToEdgeConfigComponent, RabbitMqConfigComponent, RelatedAttributesConfigComponent, RelationsQueryConfigComponent, RestApiCallConfigComponent, RpcReplyConfigComponent, RpcRequestConfigComponent, RuleChainInputComponent, RuleChainOutputComponent, RuleNodeCoreConfigActionModule, RuleNodeCoreConfigFilterModule, RuleNodeCoreConfigFlowModule, RuleNodeCoreConfigModule, RulenodeCoreConfigCommonModule, RulenodeCoreConfigEnrichmentModule, RulenodeCoreConfigTransformModule, SafeHtmlPipe, SaveToCustomTableConfigComponent, ScriptConfigComponent, SendEmailConfigComponent, SendSmsConfigComponent, SnsConfigComponent, SqsConfigComponent, SwitchConfigComponent, TenantAttributesConfigComponent, TimeseriesConfigComponent, ToEmailConfigComponent, TransformScriptConfigComponent, UnassignCustomerConfigComponent };
//# sourceMappingURL=rulenode-core-config.js.map //# sourceMappingURL=rulenode-core-config.js.map

83
ui-ngx/src/app/core/http/rule-chain.service.ts

@ -21,11 +21,8 @@ import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import { import {
ResolvedRuleChainMetaData,
RuleChain, RuleChain,
RuleChainConnectionInfo,
RuleChainMetaData, RuleChainMetaData,
ruleChainNodeComponent,
RuleChainType, RuleChainType,
ruleNodeTypeComponentTypes, ruleNodeTypeComponentTypes,
unknownNodeComponent unknownNodeComponent
@ -34,14 +31,13 @@ import { ComponentDescriptorService } from './component-descriptor.service';
import { import {
IRuleNodeConfigurationComponent, IRuleNodeConfigurationComponent,
LinkLabel, LinkLabel,
RuleNodeComponentDescriptor, RuleNodeComponentDescriptor, RuleNodeConfiguration,
TestScriptInputParams, TestScriptInputParams,
TestScriptResult TestScriptResult
} from '@app/shared/models/rule-node.models'; } from '@app/shared/models/rule-node.models';
import { ResourcesService } from '../services/resources.service'; import { ResourcesService } from '../services/resources.service';
import { catchError, map, mergeMap } from 'rxjs/operators'; import { catchError, map, mergeMap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { EntityType } from '@shared/models/entity-type.models';
import { deepClone, snakeCase } from '@core/utils'; import { deepClone, snakeCase } from '@core/utils';
import { DebugRuleNodeEventBody } from '@app/shared/models/event.models'; import { DebugRuleNodeEventBody } from '@app/shared/models/event.models';
import { Edge } from '@shared/models/edge.models'; import { Edge } from '@shared/models/edge.models';
@ -63,7 +59,8 @@ export class RuleChainService {
private translate: TranslateService private translate: TranslateService
) { } ) { }
public getRuleChains(pageLink: PageLink, type: RuleChainType = RuleChainType.CORE, config?: RequestConfig): Observable<PageData<RuleChain>> { public getRuleChains(pageLink: PageLink, type: RuleChainType = RuleChainType.CORE,
config?: RequestConfig): Observable<PageData<RuleChain>> {
return this.http.get<PageData<RuleChain>>(`/api/ruleChains${pageLink.toQuery()}&type=${type}`, return this.http.get<PageData<RuleChain>>(`/api/ruleChains${pageLink.toQuery()}&type=${type}`,
defaultHttpOptionsFromConfig(config)); defaultHttpOptionsFromConfig(config));
} }
@ -72,6 +69,10 @@ export class RuleChainService {
return this.http.get<RuleChain>(`/api/ruleChain/${ruleChainId}`, defaultHttpOptionsFromConfig(config)); return this.http.get<RuleChain>(`/api/ruleChain/${ruleChainId}`, defaultHttpOptionsFromConfig(config));
} }
public getRuleChainOutputLabels(ruleChainId: string, config?: RequestConfig): Observable<Array<string>> {
return this.http.get<Array<string>>(`/api/ruleChain/${ruleChainId}/output/labels`, defaultHttpOptionsFromConfig(config));
}
public createDefaultRuleChain(ruleChainName: string, config?: RequestConfig): Observable<RuleChain> { public createDefaultRuleChain(ruleChainName: string, config?: RequestConfig): Observable<RuleChain> {
return this.http.post<RuleChain>('/api/ruleChain/device/default', { return this.http.post<RuleChain>('/api/ruleChain/device/default', {
name: ruleChainName name: ruleChainName
@ -94,32 +95,10 @@ export class RuleChainService {
return this.http.get<RuleChainMetaData>(`/api/ruleChain/${ruleChainId}/metadata`, defaultHttpOptionsFromConfig(config)); return this.http.get<RuleChainMetaData>(`/api/ruleChain/${ruleChainId}/metadata`, defaultHttpOptionsFromConfig(config));
} }
public getResolvedRuleChainMetadata(ruleChainId: string, config?: RequestConfig): Observable<ResolvedRuleChainMetaData> {
return this.getRuleChainMetadata(ruleChainId, config).pipe(
mergeMap((ruleChainMetaData) => this.resolveRuleChainMetadata(ruleChainMetaData))
);
}
public saveRuleChainMetadata(ruleChainMetaData: RuleChainMetaData, config?: RequestConfig): Observable<RuleChainMetaData> { public saveRuleChainMetadata(ruleChainMetaData: RuleChainMetaData, config?: RequestConfig): Observable<RuleChainMetaData> {
return this.http.post<RuleChainMetaData>('/api/ruleChain/metadata', ruleChainMetaData, defaultHttpOptionsFromConfig(config)); return this.http.post<RuleChainMetaData>('/api/ruleChain/metadata', ruleChainMetaData, defaultHttpOptionsFromConfig(config));
} }
public saveAndGetResolvedRuleChainMetadata(ruleChainMetaData: RuleChainMetaData,
config?: RequestConfig): Observable<ResolvedRuleChainMetaData> {
return this.saveRuleChainMetadata(ruleChainMetaData, config).pipe(
mergeMap((savedRuleChainMetaData) => this.resolveRuleChainMetadata(savedRuleChainMetaData))
);
}
public resolveRuleChainMetadata(ruleChainMetaData: RuleChainMetaData): Observable<ResolvedRuleChainMetaData> {
return this.resolveTargetRuleChains(ruleChainMetaData.ruleChainConnections).pipe(
map((targetRuleChainsMap) => {
const resolvedRuleChainMetadata: ResolvedRuleChainMetaData = {...ruleChainMetaData, targetRuleChainsMap};
return resolvedRuleChainMetadata;
})
);
}
public getRuleNodeComponents(modulesMap: IModulesMap, ruleChainType: RuleChainType, config?: RequestConfig): public getRuleNodeComponents(modulesMap: IModulesMap, ruleChainType: RuleChainType, config?: RequestConfig):
Observable<Array<RuleNodeComponentDescriptor>> { Observable<Array<RuleNodeComponentDescriptor>> {
if (this.ruleNodeComponentsMap.get(ruleChainType)) { if (this.ruleNodeComponentsMap.get(ruleChainType)) {
@ -130,7 +109,6 @@ export class RuleChainService {
return this.resolveRuleNodeComponentsUiResources(components, modulesMap).pipe( return this.resolveRuleNodeComponentsUiResources(components, modulesMap).pipe(
map((ruleNodeComponents) => { map((ruleNodeComponents) => {
this.ruleNodeComponentsMap.set(ruleChainType, ruleNodeComponents); this.ruleNodeComponentsMap.set(ruleChainType, ruleNodeComponents);
this.ruleNodeComponentsMap.get(ruleChainType).push(ruleChainNodeComponent);
this.ruleNodeComponentsMap.get(ruleChainType).sort( this.ruleNodeComponentsMap.get(ruleChainType).sort(
(comp1, comp2) => { (comp1, comp2) => {
let result = comp1.type.toString().localeCompare(comp2.type.toString()); let result = comp1.type.toString().localeCompare(comp2.type.toString());
@ -180,6 +158,14 @@ export class RuleChainService {
return component.configurationDescriptor.nodeDefinition.customRelations; return component.configurationDescriptor.nodeDefinition.customRelations;
} }
public ruleNodeSourceRuleChainId(component: RuleNodeComponentDescriptor, config: RuleNodeConfiguration): string {
if (component.configurationDescriptor.nodeDefinition.ruleChainNode) {
return config?.ruleChainId;
} else {
return null;
}
}
public getLatestRuleNodeDebugInput(ruleNodeId: string, config?: RequestConfig): Observable<DebugRuleNodeEventBody> { public getLatestRuleNodeDebugInput(ruleNodeId: string, config?: RequestConfig): Observable<DebugRuleNodeEventBody> {
return this.http.get<DebugRuleNodeEventBody>(`/api/ruleNode/${ruleNodeId}/debugIn`, defaultHttpOptionsFromConfig(config)); return this.http.get<DebugRuleNodeEventBody>(`/api/ruleNode/${ruleNodeId}/debugIn`, defaultHttpOptionsFromConfig(config));
} }
@ -188,26 +174,6 @@ export class RuleChainService {
return this.http.post<TestScriptResult>('/api/ruleChain/testScript', inputParams, defaultHttpOptionsFromConfig(config)); return this.http.post<TestScriptResult>('/api/ruleChain/testScript', inputParams, defaultHttpOptionsFromConfig(config));
} }
private resolveTargetRuleChains(ruleChainConnections: Array<RuleChainConnectionInfo>): Observable<{[ruleChainId: string]: RuleChain}> {
if (ruleChainConnections && ruleChainConnections.length) {
const tasks: Observable<RuleChain>[] = [];
ruleChainConnections.forEach((connection) => {
tasks.push(this.resolveRuleChain(connection.targetRuleChainId.id));
});
return forkJoin(tasks).pipe(
map((ruleChains) => {
const ruleChainsMap: {[ruleChainId: string]: RuleChain} = {};
ruleChains.forEach((ruleChain) => {
ruleChainsMap[ruleChain.id.id] = ruleChain;
});
return ruleChainsMap;
})
);
} else {
return of({} as {[ruleChainId: string]: RuleChain});
}
}
private loadRuleNodeComponents(ruleChainType: RuleChainType, config?: RequestConfig): Observable<Array<RuleNodeComponentDescriptor>> { private loadRuleNodeComponents(ruleChainType: RuleChainType, config?: RequestConfig): Observable<Array<RuleNodeComponentDescriptor>> {
return this.componentDescriptorService.getComponentDescriptorsByTypes(ruleNodeTypeComponentTypes, ruleChainType, config).pipe( return this.componentDescriptorService.getComponentDescriptorsByTypes(ruleNodeTypeComponentTypes, ruleChainType, config).pipe(
map((components) => { map((components) => {
@ -228,7 +194,7 @@ export class RuleChainService {
tasks.push(this.resolveRuleNodeComponentUiResources(component, modulesMap)); tasks.push(this.resolveRuleNodeComponentUiResources(component, modulesMap));
}); });
return forkJoin(tasks).pipe( return forkJoin(tasks).pipe(
catchError((err) => { catchError(() => {
return of(components); return of(components);
}) })
); );
@ -268,7 +234,7 @@ export class RuleChainService {
)); ));
} }
return forkJoin(tasks).pipe( return forkJoin(tasks).pipe(
map((res) => { map(() => {
return component; return component;
}), }),
catchError(() => { catchError(() => {
@ -281,21 +247,6 @@ export class RuleChainService {
} }
} }
private resolveRuleChain(ruleChainId: string): Observable<RuleChain> {
return this.getRuleChain(ruleChainId, {ignoreErrors: true}).pipe(
map(ruleChain => ruleChain),
catchError((err) => {
const ruleChain = {
id: {
entityType: EntityType.RULE_CHAIN,
id: ruleChainId
}
} as RuleChain;
return of(ruleChain);
})
);
}
public getEdgeRuleChains(edgeId: string, pageLink: PageLink, config?: RequestConfig): Observable<PageData<RuleChain>> { public getEdgeRuleChains(edgeId: string, pageLink: PageLink, config?: RequestConfig): Observable<PageData<RuleChain>> {
return this.http.get<PageData<RuleChain>>(`/api/edge/${edgeId}/ruleChains${pageLink.toQuery()}`, return this.http.get<PageData<RuleChain>>(`/api/edge/${edgeId}/ruleChains${pageLink.toQuery()}`,
defaultHttpOptionsFromConfig(config)); defaultHttpOptionsFromConfig(config));

9
ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts

@ -423,7 +423,7 @@ export class ImportExportService {
public importRuleChain(expectedRuleChainType: RuleChainType): Observable<RuleChainImport> { public importRuleChain(expectedRuleChainType: RuleChainType): Observable<RuleChainImport> {
return this.openImportDialog('rulechain.import', 'rulechain.rulechain-file').pipe( return this.openImportDialog('rulechain.import', 'rulechain.rulechain-file').pipe(
mergeMap((ruleChainImport: RuleChainImport) => { map((ruleChainImport: RuleChainImport) => {
if (!this.validateImportedRuleChain(ruleChainImport)) { if (!this.validateImportedRuleChain(ruleChainImport)) {
this.store.dispatch(new ActionNotificationShow( this.store.dispatch(new ActionNotificationShow(
{message: this.translate.instant('rulechain.invalid-rulechain-file-error'), {message: this.translate.instant('rulechain.invalid-rulechain-file-error'),
@ -435,12 +435,7 @@ export class ImportExportService {
type: 'error'})); type: 'error'}));
throw new Error('Invalid rule chain type'); throw new Error('Invalid rule chain type');
} else { } else {
return this.ruleChainService.resolveRuleChainMetadata(ruleChainImport.metadata).pipe( return ruleChainImport;
map((resolvedMetadata) => {
ruleChainImport.resolvedMetadata = resolvedMetadata;
return ruleChainImport;
})
);
} }
}), }),
catchError((err) => { catchError((err) => {

8
ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts

@ -18,7 +18,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; import { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { EdgesTableConfigResolver } from '@home/pages/edge/edges-table-config.resolver' import { EdgesTableConfigResolver } from '@home/pages/edge/edges-table-config.resolver';
import { AssetsTableConfigResolver } from '@home/pages/asset/assets-table-config.resolver'; import { AssetsTableConfigResolver } from '@home/pages/asset/assets-table-config.resolver';
import { DevicesTableConfigResolver } from '@home/pages/device/devices-table-config.resolver'; import { DevicesTableConfigResolver } from '@home/pages/device/devices-table-config.resolver';
import { EntityViewsTableConfigResolver } from '@home/pages/entity-view/entity-views-table-config.resolver'; import { EntityViewsTableConfigResolver } from '@home/pages/entity-view/entity-views-table-config.resolver';
@ -32,7 +32,7 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { RuleChainType } from '@shared/models/rule-chain.models'; import { RuleChainType } from '@shared/models/rule-chain.models';
import { import {
importRuleChainBreadcumbLabelFunction, importRuleChainBreadcumbLabelFunction,
ResolvedRuleChainMetaDataResolver, RuleChainMetaDataResolver,
ruleChainBreadcumbLabelFunction, ruleChainBreadcumbLabelFunction,
RuleChainImportGuard, RuleChainImportGuard,
RuleChainResolver, RuleChainResolver,
@ -181,7 +181,7 @@ const routes: Routes = [
}, },
resolve: { resolve: {
ruleChain: RuleChainResolver, ruleChain: RuleChainResolver,
ruleChainMetaData: ResolvedRuleChainMetaDataResolver, ruleChainMetaData: RuleChainMetaDataResolver,
ruleNodeComponents: RuleNodeComponentsResolver, ruleNodeComponents: RuleNodeComponentsResolver,
tooltipster: TooltipsterResolver tooltipster: TooltipsterResolver
} }
@ -243,7 +243,7 @@ const routes: Routes = [
}, },
resolve: { resolve: {
ruleChain: RuleChainResolver, ruleChain: RuleChainResolver,
ruleChainMetaData: ResolvedRuleChainMetaDataResolver, ruleChainMetaData: RuleChainMetaDataResolver,
ruleNodeComponents: RuleNodeComponentsResolver, ruleNodeComponents: RuleNodeComponentsResolver,
tooltipster: TooltipsterResolver tooltipster: TooltipsterResolver
} }

3
ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html

@ -34,7 +34,8 @@
required required
formControlName="link" formControlName="link"
[allowedLabels]="labels" [allowedLabels]="labels"
[allowCustom]="allowCustomLabels"> [allowCustom]="allowCustomLabels"
[sourceRuleChainId]="sourceRuleChainId">
</tb-rule-node-link> </tb-rule-node-link>
</fieldset> </fieldset>
</div> </div>

74
ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts

@ -25,7 +25,8 @@ import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material
import { MatChipInputEvent, MatChipList } from '@angular/material/chips'; import { MatChipInputEvent, MatChipList } from '@angular/material/chips';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { map, mergeMap, share, startWith } from 'rxjs/operators'; import { catchError, map, mergeMap, share, startWith } from 'rxjs/operators';
import { RuleChainService } from '@core/http/rule-chain.service';
@Component({ @Component({
selector: 'tb-link-labels', selector: 'tb-link-labels',
@ -68,6 +69,9 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
@Input() @Input()
allowedLabels: {[label: string]: LinkLabel}; allowedLabels: {[label: string]: LinkLabel};
@Input()
sourceRuleChainId: string;
linksFormGroup: FormGroup; linksFormGroup: FormGroup;
modelValue: Array<string>; modelValue: Array<string>;
@ -86,7 +90,8 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
constructor(private fb: FormBuilder, constructor(private fb: FormBuilder,
public truncate: TruncatePipe, public truncate: TruncatePipe,
public translate: TranslateService) { public translate: TranslateService,
private ruleChainService: RuleChainService) {
this.linksFormGroup = this.fb.group({ this.linksFormGroup = this.fb.group({
label: [null] label: [null]
}); });
@ -115,7 +120,7 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
for (const propName of Object.keys(changes)) { for (const propName of Object.keys(changes)) {
const change = changes[propName]; const change = changes[propName];
if (change.currentValue !== change.previousValue) { if (change.currentValue !== change.previousValue) {
if (['allowCustom', 'allowedLabels'].includes(propName)) { if (['allowCustom', 'allowedLabels', 'sourceRuleChainId'].includes(propName)) {
reloadLabels = true; reloadLabels = true;
} }
} }
@ -139,25 +144,50 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
this.labelsList.length = 0; this.labelsList.length = 0;
this.labels.length = 0; this.labels.length = 0;
this.modelValue = value; this.modelValue = value;
for (const label of Object.keys(this.allowedLabels)) { this.prepareLabelsList().subscribe((labelsList) => {
this.labelsList.push({name: this.allowedLabels[label].name, value: this.allowedLabels[label].value}); this.labelsList = labelsList;
} if (value) {
if (value) { value.forEach((label) => {
value.forEach((label) => { if (this.allowedLabels[label]) {
if (this.allowedLabels[label]) { this.labels.push(deepClone(this.allowedLabels[label]));
this.labels.push(deepClone(this.allowedLabels[label])); } else {
} else { this.labels.push({
this.labels.push({ name: label,
name: label, value: label
value: label });
}); }
} });
}); }
} if (this.chipList && this.required) {
if (this.chipList && this.required) { this.chipList.errorState = !this.labels.length;
this.chipList.errorState = this.labels.length ? false : true; }
this.linksFormGroup.get('label').patchValue('', {emitEvent: true});
});
}
prepareLabelsList(): Observable<Array<LinkLabel>> {
const labelsList: Array<LinkLabel> = [];
if (this.sourceRuleChainId) {
return this.ruleChainService.getRuleChainOutputLabels(this.sourceRuleChainId, {ignoreErrors: true}).pipe(
map((labels) => {
for (const label of labels) {
labelsList.push({
name: label,
value: label
});
}
return labelsList;
}),
catchError(() => {
return of(labelsList);
})
);
} else {
for (const label of Object.keys(this.allowedLabels)) {
labelsList.push({name: this.allowedLabels[label].name, value: this.allowedLabels[label].value});
}
return of(labelsList);
} }
this.linksFormGroup.get('label').patchValue('', {emitEvent: true});
} }
displayLabelFn(label?: LinkLabel): string | undefined { displayLabelFn(label?: LinkLabel): string | undefined {
@ -165,7 +195,7 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
} }
textIsNotEmpty(text: string): boolean { textIsNotEmpty(text: string): boolean {
return (text && text != null && text.length > 0) ? true : false; return (text && text.length > 0);
} }
createLinkLabel($event: Event, value: string) { createLinkLabel($event: Event, value: string) {

2
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss

@ -34,7 +34,7 @@
background-color: #fbc766; background-color: #fbc766;
} }
&.tb-rule-chain-type { &.tb-flow-type {
background-color: #d6c4f1; background-color: #d6c4f1;
} }

9
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts

@ -37,6 +37,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component';
import { deepClone } from '@core/utils'; import { deepClone } from '@core/utils';
import { RuleChainType } from '@shared/models/rule-chain.models';
@Component({ @Component({
selector: 'tb-rule-node-config', selector: 'tb-rule-node-config',
@ -69,6 +70,12 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On
@Input() @Input()
ruleNodeId: string; ruleNodeId: string;
@Input()
ruleChainId: string;
@Input()
ruleChainType: RuleChainType;
nodeDefinitionValue: RuleNodeDefinition; nodeDefinitionValue: RuleNodeDefinition;
@Input() @Input()
@ -186,6 +193,8 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On
this.definedConfigComponentRef = this.definedConfigContainer.createComponent(factory); this.definedConfigComponentRef = this.definedConfigContainer.createComponent(factory);
this.definedConfigComponent = this.definedConfigComponentRef.instance; this.definedConfigComponent = this.definedConfigComponentRef.instance;
this.definedConfigComponent.ruleNodeId = this.ruleNodeId; this.definedConfigComponent.ruleNodeId = this.ruleNodeId;
this.definedConfigComponent.ruleChainId = this.ruleChainId;
this.definedConfigComponent.ruleChainType = this.ruleChainType;
this.definedConfigComponent.configuration = this.configuration; this.definedConfigComponent.configuration = this.configuration;
this.changeSubscription = this.definedConfigComponent.configurationChanged.subscribe((configuration) => { this.changeSubscription = this.definedConfigComponent.configurationChanged.subscribe((configuration) => {
this.updateModel(configuration); this.updateModel(configuration);

20
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-details-buttons" *ngIf="!isAdd && ruleNode.component.type === ruleNodeType.RULE_CHAIN"> <div class="tb-details-buttons" *ngIf="!isAdd && ruleNode.component?.configurationDescriptor?.nodeDefinition?.ruleChainNode">
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
(click)="openRuleChain($event)"> (click)="openRuleChain($event)">
{{'rulechain.open-rulechain' | translate }} {{'rulechain.open-rulechain' | translate }}
@ -23,7 +23,7 @@
</div> </div>
<form [formGroup]="ruleNodeFormGroup" class="mat-padding"> <form [formGroup]="ruleNodeFormGroup" class="mat-padding">
<fieldset [disabled]="(isLoading$ | async) || !isEdit || isReadOnly"> <fieldset [disabled]="(isLoading$ | async) || !isEdit || isReadOnly">
<section *ngIf="ruleNode.component.type !== ruleNodeType.RULE_CHAIN"> <section>
<section fxLayout="column" fxLayout.gt-sm="row"> <section fxLayout="column" fxLayout.gt-sm="row">
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block">
<mat-label translate>rulenode.name</mat-label> <mat-label translate>rulenode.name</mat-label>
@ -43,6 +43,8 @@
<tb-rule-node-config #ruleNodeConfigComponent <tb-rule-node-config #ruleNodeConfigComponent
formControlName="configuration" formControlName="configuration"
[ruleNodeId]="ruleNode.ruleNodeId?.id" [ruleNodeId]="ruleNode.ruleNodeId?.id"
[ruleChainId]="ruleChainId"
[ruleChainType]="ruleChainType"
[nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition"> [nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition">
</tb-rule-node-config> </tb-rule-node-config>
<div formGroupName="additionalInfo" fxLayout="column"> <div formGroupName="additionalInfo" fxLayout="column">
@ -52,19 +54,5 @@
</mat-form-field> </mat-form-field>
</div> </div>
</section> </section>
<section *ngIf="ruleNode.component.type === ruleNodeType.RULE_CHAIN">
<tb-entity-autocomplete required
[excludeEntityIds]="[ruleChainId]"
[entityType]="entityType.RULE_CHAIN"
[entitySubtype]="ruleChainType"
formControlName="targetRuleChainId">
</tb-entity-autocomplete>
<div formGroupName="additionalInfo" fxLayout="column">
<mat-form-field class="mat-block">
<mat-label translate>rulenode.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea>
</mat-form-field>
</div>
</section>
</fieldset> </fieldset>
</form> </form>

62
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts

@ -75,28 +75,16 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
this.ruleNodeFormSubscription = null; this.ruleNodeFormSubscription = null;
} }
if (this.ruleNode) { if (this.ruleNode) {
if (this.ruleNode.component.type !== RuleNodeType.RULE_CHAIN) { this.ruleNodeFormGroup = this.fb.group({
name: [this.ruleNode.name, [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*'), Validators.maxLength(255)]],
this.ruleNodeFormGroup = this.fb.group({ debugMode: [this.ruleNode.debugMode, []],
name: [this.ruleNode.name, [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*'), Validators.maxLength(255)]], configuration: [this.ruleNode.configuration, [Validators.required]],
debugMode: [this.ruleNode.debugMode, []], additionalInfo: this.fb.group(
configuration: [this.ruleNode.configuration, [Validators.required]], {
additionalInfo: this.fb.group( description: [this.ruleNode.additionalInfo ? this.ruleNode.additionalInfo.description : ''],
{ }
description: [this.ruleNode.additionalInfo ? this.ruleNode.additionalInfo.description : ''], )
} });
)
});
} else {
this.ruleNodeFormGroup = this.fb.group({
targetRuleChainId: [this.ruleNode.targetRuleChainId, [Validators.required]],
additionalInfo: this.fb.group(
{
description: [this.ruleNode.additionalInfo ? this.ruleNode.additionalInfo.description : ''],
}
)
});
}
this.ruleNodeFormSubscription = this.ruleNodeFormGroup.valueChanges.subscribe(() => { this.ruleNodeFormSubscription = this.ruleNodeFormGroup.valueChanges.subscribe(() => {
this.updateRuleNode(); this.updateRuleNode();
}); });
@ -107,23 +95,8 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
private updateRuleNode() { private updateRuleNode() {
const formValue = this.ruleNodeFormGroup.value || {}; const formValue = this.ruleNodeFormGroup.value || {};
formValue.name = formValue.name.trim();
if (this.ruleNode.component.type === RuleNodeType.RULE_CHAIN) { Object.assign(this.ruleNode, formValue);
const targetRuleChainId: string = formValue.targetRuleChainId;
if (this.ruleNode.targetRuleChainId !== targetRuleChainId && targetRuleChainId) {
this.ruleChainService.getRuleChain(targetRuleChainId).subscribe(
(ruleChain) => {
this.ruleNode.name = ruleChain.name;
Object.assign(this.ruleNode, formValue);
}
);
} else {
Object.assign(this.ruleNode, formValue);
}
} else {
formValue.name = formValue.name.trim();
Object.assign(this.ruleNode, formValue);
}
} }
ngOnInit(): void { ngOnInit(): void {
@ -141,20 +114,19 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
} }
validate() { validate() {
if (this.ruleNode.component.type !== RuleNodeType.RULE_CHAIN) { this.ruleNodeConfigComponent.validate();
this.ruleNodeConfigComponent.validate();
}
} }
openRuleChain($event: Event) { openRuleChain($event: Event) {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
if (this.ruleNode.targetRuleChainId) { const ruleChainId = this.ruleNodeFormGroup.get('configuration')?.value?.ruleChainId;
if (ruleChainId) {
if (this.ruleChainType === RuleChainType.EDGE) { if (this.ruleChainType === RuleChainType.EDGE) {
this.router.navigateByUrl(`/edgeManagement/ruleChains/${this.ruleNode.targetRuleChainId}`); this.router.navigateByUrl(`/edgeManagement/ruleChains/${ruleChainId}`);
} else { } else {
this.router.navigateByUrl(`/ruleChains/${this.ruleNode.targetRuleChainId}`); this.router.navigateByUrl(`/ruleChains/${ruleChainId}`);
} }
} }
} }

1
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html

@ -22,6 +22,7 @@
formControlName="labels" formControlName="labels"
[allowedLabels]="allowedLabels" [allowedLabels]="allowedLabels"
[allowCustom]="allowCustom" [allowCustom]="allowCustom"
[sourceRuleChainId]="sourceRuleChainId"
></tb-link-labels> ></tb-link-labels>
</form> </form>
</div> </div>

3
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts

@ -57,6 +57,9 @@ export class RuleNodeLinkComponent implements ControlValueAccessor, OnInit {
@Input() @Input()
allowedLabels: {[label: string]: LinkLabel}; allowedLabels: {[label: string]: LinkLabel};
@Input()
sourceRuleChainId: string;
ruleNodeLinkFormGroup: FormGroup; ruleNodeLinkFormGroup: FormGroup;
modelValue: FcRuleEdge; modelValue: FcRuleEdge;

3
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html

@ -158,7 +158,8 @@
#tbRuleNodeLink #tbRuleNodeLink
[(ngModel)]="editingRuleNodeLink" [(ngModel)]="editingRuleNodeLink"
[allowedLabels]="editingRuleNodeLinkLabels" [allowedLabels]="editingRuleNodeLinkLabels"
[allowCustom]="editingRuleNodeAllowCustomLabels"> [allowCustom]="editingRuleNodeAllowCustomLabels"
[sourceRuleChainId]="editingRuleNodeSourceRuleChainId">
</tb-rule-node-link> </tb-rule-node-link>
</tb-details-panel> </tb-details-panel>
</mat-drawer> </mat-drawer>

2
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss

@ -184,7 +184,7 @@
display: block; display: block;
position: absolute; position: absolute;
top: 11px; top: 11px;
right: -12px; right: 10px;
border: 1px solid #FFFFFF; border: 1px solid #FFFFFF;
border-radius: 4px; border-radius: 4px;
line-height: 18px; line-height: 18px;

147
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts

@ -43,12 +43,10 @@ import { ActivatedRoute, Router } from '@angular/router';
import { import {
inputNodeComponent, inputNodeComponent,
NodeConnectionInfo, NodeConnectionInfo,
ResolvedRuleChainMetaData,
RuleChain, RuleChain,
RuleChainConnectionInfo,
RuleChainImport, RuleChainImport,
RuleChainMetaData, RuleChainMetaData,
ruleChainNodeComponent, RuleChainType RuleChainType
} from '@shared/models/rule-chain.models'; } from '@shared/models/rule-chain.models';
import { FcItemInfo, FlowchartConstants, NgxFlowchartComponent, UserCallbacks } from 'ngx-flowchart'; import { FcItemInfo, FlowchartConstants, NgxFlowchartComponent, UserCallbacks } from 'ngx-flowchart';
import { import {
@ -75,7 +73,6 @@ import { DialogComponent } from '@shared/components/dialog.component';
import { MatMenuTrigger } from '@angular/material/menu'; import { MatMenuTrigger } from '@angular/material/menu';
import { ItemBufferService, RuleNodeConnection } from '@core/services/item-buffer.service'; import { ItemBufferService, RuleNodeConnection } from '@core/services/item-buffer.service';
import { Hotkey } from 'angular2-hotkeys'; import { Hotkey } from 'angular2-hotkeys';
import { EntityType } from '@shared/models/entity-type.models';
import { DebugEventType, EventType } from '@shared/models/event.models'; import { DebugEventType, EventType } from '@shared/models/event.models';
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
@ -130,6 +127,7 @@ export class RuleChainPageComponent extends PageComponent
editingRuleNodeIndex = -1; editingRuleNodeIndex = -1;
editingRuleNodeAllowCustomLabels = false; editingRuleNodeAllowCustomLabels = false;
editingRuleNodeLinkLabels: {[label: string]: LinkLabel}; editingRuleNodeLinkLabels: {[label: string]: LinkLabel};
editingRuleNodeSourceRuleChainId: string;
@ViewChild('tbRuleNode') ruleNodeComponent: RuleNodeDetailsComponent; @ViewChild('tbRuleNode') ruleNodeComponent: RuleNodeDetailsComponent;
@ViewChild('tbRuleNodeLink') ruleNodeLinkComponent: RuleNodeLinkComponent; @ViewChild('tbRuleNodeLink') ruleNodeLinkComponent: RuleNodeLinkComponent;
@ -147,7 +145,7 @@ export class RuleChainPageComponent extends PageComponent
ruleNodeTypeSearch = ''; ruleNodeTypeSearch = '';
ruleChain: RuleChain; ruleChain: RuleChain;
ruleChainMetaData: ResolvedRuleChainMetaData; ruleChainMetaData: RuleChainMetaData;
ruleChainModel: FcRuleNodeModel = { ruleChainModel: FcRuleNodeModel = {
nodes: [], nodes: [],
@ -179,16 +177,11 @@ export class RuleChainPageComponent extends PageComponent
createEdge: (event, edge: FcRuleEdge) => { createEdge: (event, edge: FcRuleEdge) => {
const sourceNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source) as FcRuleNode; const sourceNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source) as FcRuleNode;
if (sourceNode.component.type === RuleNodeType.INPUT) { if (sourceNode.component.type === RuleNodeType.INPUT) {
const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination) as FcRuleNode; const found = this.ruleChainModel.edges.find(theEdge => theEdge.source === (this.inputConnectorId + ''));
if (destNode.component.type === RuleNodeType.RULE_CHAIN) { if (found) {
return NEVER; this.ruleChainCanvas.modelService.edges.delete(found);
} else {
const found = this.ruleChainModel.edges.find(theEdge => theEdge.source === (this.inputConnectorId + ''));
if (found) {
this.ruleChainCanvas.modelService.edges.delete(found);
}
return of(edge);
} }
return of(edge);
} else { } else {
if (edge.label) { if (edge.label) {
if (!edge.labels) { if (!edge.labels) {
@ -198,8 +191,9 @@ export class RuleChainPageComponent extends PageComponent
} else { } else {
const labels = this.ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); const labels = this.ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
const allowCustomLabels = this.ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component); const allowCustomLabels = this.ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
const sourceRuleChainId = this.ruleChainService.ruleNodeSourceRuleChainId(sourceNode.component, sourceNode.configuration);
this.enableHotKeys = false; this.enableHotKeys = false;
return this.addRuleNodeLink(edge, labels, allowCustomLabels).pipe( return this.addRuleNodeLink(edge, labels, allowCustomLabels, sourceRuleChainId).pipe(
tap(() => { tap(() => {
this.enableHotKeys = true; this.enableHotKeys = true;
}), }),
@ -294,7 +288,7 @@ export class RuleChainPageComponent extends PageComponent
if (this.isImport) { if (this.isImport) {
const ruleChainImport: RuleChainImport = this.itembuffer.getRuleChainImport(); const ruleChainImport: RuleChainImport = this.itembuffer.getRuleChainImport();
this.ruleChain = ruleChainImport.ruleChain; this.ruleChain = ruleChainImport.ruleChain;
this.ruleChainMetaData = ruleChainImport.resolvedMetadata; this.ruleChainMetaData = ruleChainImport.metadata;
} else { } else {
this.ruleChain = this.route.snapshot.data.ruleChain; this.ruleChain = this.route.snapshot.data.ruleChain;
this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData; this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData;
@ -601,63 +595,6 @@ export class RuleChainPageComponent extends PageComponent
} }
}); });
} }
if (this.ruleChainMetaData.ruleChainConnections) {
const ruleChainsMap = this.ruleChainMetaData.targetRuleChainsMap;
const ruleChainNodesMap: {[ruleChainNodeId: string]: FcRuleNode} = {};
const ruleChainEdgeMap: {[edgeKey: string]: FcRuleEdge} = {};
this.ruleChainMetaData.ruleChainConnections.forEach((ruleChainConnection) => {
const ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id];
if (ruleChainConnection.additionalInfo && ruleChainConnection.additionalInfo.ruleChainNodeId) {
let ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId];
if (!ruleChainNode) {
ruleChainNode = {
id: 'rule-chain-node-' + this.nextNodeID++,
name: ruleChain.name ? ruleChain.name : 'Unresolved',
targetRuleChainId: ruleChain.name ? ruleChainConnection.targetRuleChainId.id : null,
error: ruleChain.name ? undefined : this.translate.instant('rulenode.invalid-target-rulechain'),
additionalInfo: ruleChainConnection.additionalInfo,
x: Math.round(ruleChainConnection.additionalInfo.layoutX),
y: Math.round(ruleChainConnection.additionalInfo.layoutY),
component: ruleChainNodeComponent,
nodeClass: ruleNodeTypeDescriptors.get(RuleNodeType.RULE_CHAIN).nodeClass,
icon: ruleNodeTypeDescriptors.get(RuleNodeType.RULE_CHAIN).icon,
connectors: [
{
type: FlowchartConstants.leftConnectorType,
id: (this.nextConnectorID++) + ''
}
],
ruleChainType: this.ruleChainType
};
ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId] = ruleChainNode;
this.ruleChainModel.nodes.push(ruleChainNode);
}
const sourceNode = nodes[ruleChainConnection.fromIndex];
if (sourceNode) {
const connectors = sourceNode.connectors.filter(connector => connector.type === FlowchartConstants.rightConnectorType);
if (connectors && connectors.length) {
const sourceId = connectors[0].id;
const destId = ruleChainNode.connectors[0].id;
const edgeKey = sourceId + '_' + destId;
let ruleChainEdge = ruleChainEdgeMap[edgeKey];
if (!ruleChainEdge) {
ruleChainEdge = {
source: sourceId,
destination: destId,
label: ruleChainConnection.type,
labels: [ruleChainConnection.type]
};
ruleChainEdgeMap[edgeKey] = ruleChainEdge;
this.ruleChainModel.edges.push(ruleChainEdge);
} else {
ruleChainEdge.label += ' / ' + ruleChainConnection.type;
ruleChainEdge.labels.push(ruleChainConnection.type);
}
}
}
}
});
}
if (this.ruleChainCanvas) { if (this.ruleChainCanvas) {
this.ruleChainCanvas.adjustCanvasSize(true); this.ruleChainCanvas.adjustCanvasSize(true);
} }
@ -918,6 +855,8 @@ export class RuleChainPageComponent extends PageComponent
this.editingRuleNode = null; this.editingRuleNode = null;
this.editingRuleNodeLinkLabels = this.ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); this.editingRuleNodeLinkLabels = this.ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
this.editingRuleNodeAllowCustomLabels = this.ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component); this.editingRuleNodeAllowCustomLabels = this.ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
this.editingRuleNodeSourceRuleChainId =
this.ruleChainService.ruleNodeSourceRuleChainId(sourceNode.component, sourceNode.configuration);
this.isEditingRuleNodeLink = true; this.isEditingRuleNodeLink = true;
this.editingRuleNodeLinkIndex = this.ruleChainModel.edges.indexOf(edge); this.editingRuleNodeLinkIndex = this.ruleChainModel.edges.indexOf(edge);
this.editingRuleNodeLink = deepClone(edge); this.editingRuleNodeLink = deepClone(edge);
@ -1093,7 +1032,7 @@ export class RuleChainPageComponent extends PageComponent
const type = ruleNodeTypeDescriptors.get(ruleNodeType); const type = ruleNodeTypeDescriptors.get(ruleNodeType);
this.displayTooltip(event, this.displayTooltip(event,
'<div class="tb-rule-node-tooltip tb-lib-tooltip">' + '<div class="tb-rule-node-tooltip tb-lib-tooltip">' +
'<div id="tb-node-content" layout="column">' + '<div id="tb-node-content">' +
'<div class="tb-node-title">' + this.translate.instant(type.name) + '</div>' + '<div class="tb-node-title">' + this.translate.instant(type.name) + '</div>' +
'<div class="tb-node-details">' + this.translate.instant(type.details) + '</div>' + '<div class="tb-node-details">' + this.translate.instant(type.details) + '</div>' +
'</div>' + '</div>' +
@ -1104,7 +1043,7 @@ export class RuleChainPageComponent extends PageComponent
displayLibNodeDescriptionTooltip(event: MouseEvent, node: FcRuleNodeType) { displayLibNodeDescriptionTooltip(event: MouseEvent, node: FcRuleNodeType) {
this.displayTooltip(event, this.displayTooltip(event,
'<div class="tb-rule-node-tooltip tb-lib-tooltip">' + '<div class="tb-rule-node-tooltip tb-lib-tooltip">' +
'<div id="tb-node-content" layout="column">' + '<div id="tb-node-content">' +
'<div class="tb-node-title">' + node.component.name + '</div>' + '<div class="tb-node-title">' + node.component.name + '</div>' +
'<div class="tb-node-description">' + node.component.configurationDescriptor.nodeDefinition.description + '</div>' + '<div class="tb-node-description">' + node.component.configurationDescriptor.nodeDefinition.description + '</div>' +
'<div class="tb-node-details">' + node.component.configurationDescriptor.nodeDefinition.details + '</div>' + '<div class="tb-node-details">' + node.component.configurationDescriptor.nodeDefinition.details + '</div>' +
@ -1129,7 +1068,7 @@ export class RuleChainPageComponent extends PageComponent
} }
} }
let tooltipContent = '<div class="tb-rule-node-tooltip">' + let tooltipContent = '<div class="tb-rule-node-tooltip">' +
'<div id="tb-node-content" layout="column">' + '<div id="tb-node-content">' +
'<div class="tb-node-title">' + name + '</div>' + '<div class="tb-node-title">' + name + '</div>' +
'<div class="tb-node-description">' + desc + '</div>'; '<div class="tb-node-description">' + desc + '</div>';
if (details) { if (details) {
@ -1189,7 +1128,7 @@ export class RuleChainPageComponent extends PageComponent
resetDebugModeInAllNodes() { resetDebugModeInAllNodes() {
let changed = false; let changed = false;
this.ruleChainModel.nodes.forEach((node) => { this.ruleChainModel.nodes.forEach((node) => {
if (node.component.type !== RuleNodeType.INPUT && node.component.type !== RuleNodeType.RULE_CHAIN) { if (node.component.type !== RuleNodeType.INPUT) {
changed = changed || node.debugMode; changed = changed || node.debugMode;
node.debugMode = false; node.debugMode = false;
} }
@ -1223,12 +1162,11 @@ export class RuleChainPageComponent extends PageComponent
const ruleChainMetaData: RuleChainMetaData = { const ruleChainMetaData: RuleChainMetaData = {
ruleChainId: this.ruleChain.id, ruleChainId: this.ruleChain.id,
nodes: [], nodes: [],
connections: [], connections: []
ruleChainConnections: []
}; };
const nodes: FcRuleNode[] = []; const nodes: FcRuleNode[] = [];
this.ruleChainModel.nodes.forEach((node) => { this.ruleChainModel.nodes.forEach((node) => {
if (node.component.type !== RuleNodeType.INPUT && node.component.type !== RuleNodeType.RULE_CHAIN) { if (node.component.type !== RuleNodeType.INPUT) {
const ruleNode: RuleNode = { const ruleNode: RuleNode = {
id: node.ruleNodeId, id: node.ruleNodeId,
type: node.component.clazz, type: node.component.clazz,
@ -1253,35 +1191,19 @@ export class RuleChainPageComponent extends PageComponent
const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination); const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination);
if (sourceNode.component.type !== RuleNodeType.INPUT) { if (sourceNode.component.type !== RuleNodeType.INPUT) {
const fromIndex = nodes.indexOf(sourceNode); const fromIndex = nodes.indexOf(sourceNode);
if (destNode.component.type === RuleNodeType.RULE_CHAIN) { const toIndex = nodes.indexOf(destNode);
const ruleChainConnection = { const nodeConnection = {
fromIndex, fromIndex,
targetRuleChainId: {entityType: EntityType.RULE_CHAIN, id: destNode.targetRuleChainId}, toIndex
additionalInfo: destNode.additionalInfo ? destNode.additionalInfo : {} } as NodeConnectionInfo;
} as RuleChainConnectionInfo; edge.labels.forEach((label) => {
ruleChainConnection.additionalInfo.layoutX = Math.round(destNode.x); const newNodeConnection = deepClone(nodeConnection);
ruleChainConnection.additionalInfo.layoutY = Math.round(destNode.y); newNodeConnection.type = label;
ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id; ruleChainMetaData.connections.push(newNodeConnection);
edge.labels.forEach((label) => { });
const newRuleChainConnection = deepClone(ruleChainConnection);
newRuleChainConnection.type = label;
ruleChainMetaData.ruleChainConnections.push(newRuleChainConnection);
});
} else {
const toIndex = nodes.indexOf(destNode);
const nodeConnection = {
fromIndex,
toIndex
} as NodeConnectionInfo;
edge.labels.forEach((label) => {
const newNodeConnection = deepClone(nodeConnection);
newNodeConnection.type = label;
ruleChainMetaData.connections.push(newNodeConnection);
});
}
} }
}); });
this.ruleChainService.saveAndGetResolvedRuleChainMetadata(ruleChainMetaData).subscribe((savedRuleChainMetaData) => { this.ruleChainService.saveRuleChainMetadata(ruleChainMetaData).subscribe((savedRuleChainMetaData) => {
this.ruleChainMetaData = savedRuleChainMetaData; this.ruleChainMetaData = savedRuleChainMetaData;
if (this.isImport) { if (this.isImport) {
this.isDirtyValue = false; this.isDirtyValue = false;
@ -1346,7 +1268,8 @@ export class RuleChainPageComponent extends PageComponent
); );
} }
addRuleNodeLink(link: FcRuleEdge, labels: {[label: string]: LinkLabel}, allowCustomLabels: boolean): Observable<FcRuleEdge> { addRuleNodeLink(link: FcRuleEdge, labels: {[label: string]: LinkLabel},
allowCustomLabels: boolean, sourceRuleChainId: string): Observable<FcRuleEdge> {
return this.dialog.open<AddRuleNodeLinkDialogComponent, AddRuleNodeLinkDialogData, return this.dialog.open<AddRuleNodeLinkDialogComponent, AddRuleNodeLinkDialogData,
FcRuleEdge>(AddRuleNodeLinkDialogComponent, { FcRuleEdge>(AddRuleNodeLinkDialogComponent, {
disableClose: true, disableClose: true,
@ -1354,7 +1277,8 @@ export class RuleChainPageComponent extends PageComponent
data: { data: {
link, link,
labels, labels,
allowCustomLabels allowCustomLabels,
sourceRuleChainId
} }
}).afterClosed(); }).afterClosed();
} }
@ -1384,7 +1308,7 @@ export class RuleChainPageComponent extends PageComponent
} }
); );
const content = '<div class="tb-rule-node-error-tooltip">' + const content = '<div class="tb-rule-node-error-tooltip">' +
'<div id="tooltip-content" layout="column">' + '<div id="tooltip-content">' +
'<div class="tb-node-details">' + node.error + '</div>' + '<div class="tb-node-details">' + node.error + '</div>' +
'</div>' + '</div>' +
'</div>'; '</div>';
@ -1452,6 +1376,7 @@ export interface AddRuleNodeLinkDialogData {
link: FcRuleEdge; link: FcRuleEdge;
labels: {[label: string]: LinkLabel}; labels: {[label: string]: LinkLabel};
allowCustomLabels: boolean; allowCustomLabels: boolean;
sourceRuleChainId: string;
} }
@Component({ @Component({
@ -1468,6 +1393,7 @@ export class AddRuleNodeLinkDialogComponent extends DialogComponent<AddRuleNodeL
link: FcRuleEdge; link: FcRuleEdge;
labels: {[label: string]: LinkLabel}; labels: {[label: string]: LinkLabel};
allowCustomLabels: boolean; allowCustomLabels: boolean;
sourceRuleChainId: string;
submitted = false; submitted = false;
@ -1482,6 +1408,7 @@ export class AddRuleNodeLinkDialogComponent extends DialogComponent<AddRuleNodeL
this.link = this.data.link; this.link = this.data.link;
this.labels = this.data.labels; this.labels = this.data.labels;
this.allowCustomLabels = this.data.allowCustomLabels; this.allowCustomLabels = this.data.allowCustomLabels;
this.sourceRuleChainId = this.data.sourceRuleChainId;
this.ruleNodeLinkFormGroup = this.fb.group({ this.ruleNodeLinkFormGroup = this.fb.group({
link: [deepClone(this.link), [Validators.required]] link: [deepClone(this.link), [Validators.required]]

12
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts

@ -32,7 +32,7 @@ import { RuleChainsTableConfigResolver } from '@modules/home/pages/rulechain/rul
import { from, Observable } from 'rxjs'; import { from, Observable } from 'rxjs';
import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb';
import { import {
ResolvedRuleChainMetaData, RuleChainMetaData,
RuleChain, RuleChainType RuleChain, RuleChainType
} from '@shared/models/rule-chain.models'; } from '@shared/models/rule-chain.models';
import { RuleChainService } from '@core/http/rule-chain.service'; import { RuleChainService } from '@core/http/rule-chain.service';
@ -56,14 +56,14 @@ export class RuleChainResolver implements Resolve<RuleChain> {
} }
@Injectable() @Injectable()
export class ResolvedRuleChainMetaDataResolver implements Resolve<ResolvedRuleChainMetaData> { export class RuleChainMetaDataResolver implements Resolve<RuleChainMetaData> {
constructor(private ruleChainService: RuleChainService) { constructor(private ruleChainService: RuleChainService) {
} }
resolve(route: ActivatedRouteSnapshot): Observable<ResolvedRuleChainMetaData> { resolve(route: ActivatedRouteSnapshot): Observable<RuleChainMetaData> {
const ruleChainId = route.params.ruleChainId; const ruleChainId = route.params.ruleChainId;
return this.ruleChainService.getResolvedRuleChainMetadata(ruleChainId); return this.ruleChainService.getRuleChainMetadata(ruleChainId);
} }
} }
@ -160,7 +160,7 @@ const routes: Routes = [
}, },
resolve: { resolve: {
ruleChain: RuleChainResolver, ruleChain: RuleChainResolver,
ruleChainMetaData: ResolvedRuleChainMetaDataResolver, ruleChainMetaData: RuleChainMetaDataResolver,
ruleNodeComponents: RuleNodeComponentsResolver, ruleNodeComponents: RuleNodeComponentsResolver,
tooltipster: TooltipsterResolver tooltipster: TooltipsterResolver
} }
@ -196,7 +196,7 @@ const routes: Routes = [
providers: [ providers: [
RuleChainsTableConfigResolver, RuleChainsTableConfigResolver,
RuleChainResolver, RuleChainResolver,
ResolvedRuleChainMetaDataResolver, RuleChainMetaDataResolver,
RuleNodeComponentsResolver, RuleNodeComponentsResolver,
TooltipsterResolver, TooltipsterResolver,
RuleChainImportGuard RuleChainImportGuard

4
ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html

@ -59,8 +59,8 @@
&times; &times;
</div> </div>
</section> </section>
<section *ngIf="node.component.type === RuleNodeType.RULE_CHAIN && modelservice.isEditable()"> <section *ngIf="node.component?.configurationDescriptor?.nodeDefinition?.ruleChainNode && modelservice.isEditable()">
<div class="fc-nodeopen" (click)="openRuleChain($event, node)"> <div class="fc-nodeopen" (click)="openRuleChain($event, node)" (mouseenter)="displayOpenRuleChainTooltip($event, node)">
<mat-icon class="material-icons" svgIcon="mdi:login"></mat-icon> <mat-icon class="material-icons" svgIcon="mdi:login"></mat-icon>
</div> </div>
</section> </section>

43
ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts

@ -20,6 +20,7 @@ import { FcNodeComponent } from 'ngx-flowchart';
import { FcRuleNode, RuleNodeType } from '@shared/models/rule-node.models'; import { FcRuleNode, RuleNodeType } from '@shared/models/rule-node.models';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { RuleChainType } from '@app/shared/models/rule-chain.models'; import { RuleChainType } from '@app/shared/models/rule-chain.models';
import { TranslateService } from '@ngx-translate/core';
@Component({ @Component({
// tslint:disable-next-line:component-selector // tslint:disable-next-line:component-selector
@ -33,6 +34,7 @@ export class RuleNodeComponent extends FcNodeComponent implements OnInit {
RuleNodeType = RuleNodeType; RuleNodeType = RuleNodeType;
constructor(private sanitizer: DomSanitizer, constructor(private sanitizer: DomSanitizer,
private translate: TranslateService,
private router: Router) { private router: Router) {
super(); super();
} }
@ -48,13 +50,48 @@ export class RuleNodeComponent extends FcNodeComponent implements OnInit {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
if (node.targetRuleChainId) { if (node.configuration?.ruleChainId) {
if (node.ruleChainType === RuleChainType.EDGE) { if (node.ruleChainType === RuleChainType.EDGE) {
this.router.navigateByUrl(`/edgeManagement/ruleChains/${node.targetRuleChainId}`); this.router.navigateByUrl(`/edgeManagement/ruleChains/${node.configuration?.ruleChainId}`);
} else { } else {
this.router.navigateByUrl(`/ruleChains/${node.targetRuleChainId}`); this.router.navigateByUrl(`/ruleChains/${node.configuration?.ruleChainId}`);
} }
} }
} }
displayOpenRuleChainTooltip($event: MouseEvent, node: FcRuleNode) {
if ($event) {
$event.stopPropagation();
}
this.userNodeCallbacks.mouseLeave($event, node);
const tooltipContent = '<div class="tb-rule-node-tooltip">' +
'<div id="tb-node-content">' +
'<div class="tb-node-description">' + this.translate.instant('rulechain.open-rulechain') + '</div></div></div>';
const element = $($event.target);
element.tooltipster(
{
theme: 'tooltipster-shadow',
delay: 100,
trigger: 'custom',
triggerOpen: {
click: false,
tap: false
},
triggerClose: {
click: true,
tap: true,
scroll: true,
mouseleave: true
},
side: 'top',
distance: 12,
trackOrigin: true
}
);
const tooltip = element.tooltipster('instance');
const contentElement = $(tooltipContent);
tooltip.content(contentElement);
tooltip.open();
}
} }

3
ui-ngx/src/app/shared/models/component-descriptor.models.ts

@ -21,7 +21,8 @@ export enum ComponentType {
FILTER = 'FILTER', FILTER = 'FILTER',
TRANSFORMATION = 'TRANSFORMATION', TRANSFORMATION = 'TRANSFORMATION',
ACTION = 'ACTION', ACTION = 'ACTION',
EXTERNAL = 'EXTERNAL' EXTERNAL = 'EXTERNAL',
FLOW = 'FLOW'
} }
export enum ComponentScope { export enum ComponentScope {

33
ui-ngx/src/app/shared/models/rule-chain.models.ts

@ -38,17 +38,11 @@ export interface RuleChainMetaData {
firstNodeIndex?: number; firstNodeIndex?: number;
nodes: Array<RuleNode>; nodes: Array<RuleNode>;
connections: Array<NodeConnectionInfo>; connections: Array<NodeConnectionInfo>;
ruleChainConnections: Array<RuleChainConnectionInfo>;
}
export interface ResolvedRuleChainMetaData extends RuleChainMetaData {
targetRuleChainsMap: {[ruleChainId: string]: RuleChain};
} }
export interface RuleChainImport { export interface RuleChainImport {
ruleChain: RuleChain; ruleChain: RuleChain;
metadata: RuleChainMetaData; metadata: RuleChainMetaData;
resolvedMetadata?: ResolvedRuleChainMetaData;
} }
export interface NodeConnectionInfo { export interface NodeConnectionInfo {
@ -57,39 +51,16 @@ export interface NodeConnectionInfo {
type: string; type: string;
} }
export interface RuleChainConnectionInfo {
fromIndex: number;
targetRuleChainId: RuleChainId;
additionalInfo: any;
type: string;
}
export const ruleNodeTypeComponentTypes: ComponentType[] = export const ruleNodeTypeComponentTypes: ComponentType[] =
[ [
ComponentType.FILTER, ComponentType.FILTER,
ComponentType.ENRICHMENT, ComponentType.ENRICHMENT,
ComponentType.TRANSFORMATION, ComponentType.TRANSFORMATION,
ComponentType.ACTION, ComponentType.ACTION,
ComponentType.EXTERNAL ComponentType.EXTERNAL,
ComponentType.FLOW
]; ];
export const ruleChainNodeComponent: RuleNodeComponentDescriptor = {
type: RuleNodeType.RULE_CHAIN,
name: 'rule chain',
clazz: 'tb.internal.RuleChain',
configurationDescriptor: {
nodeDefinition: {
description: '',
details: 'Forwards incoming messages to specified Rule Chain',
inEnabled: true,
outEnabled: false,
relationTypes: [],
customRelations: false,
defaultConfiguration: {}
}
}
};
export const unknownNodeComponent: RuleNodeComponentDescriptor = { export const unknownNodeComponent: RuleNodeComponentDescriptor = {
type: RuleNodeType.UNKNOWN, type: RuleNodeType.UNKNOWN,
name: 'unknown', name: 'unknown',

23
ui-ngx/src/app/shared/models/rule-node.models.ts

@ -52,6 +52,7 @@ export interface RuleNodeDefinition {
outEnabled: boolean; outEnabled: boolean;
relationTypes: string[]; relationTypes: string[];
customRelations: boolean; customRelations: boolean;
ruleChainNode?: boolean;
defaultConfiguration: RuleNodeConfiguration; defaultConfiguration: RuleNodeConfiguration;
icon?: string; icon?: string;
iconUrl?: string; iconUrl?: string;
@ -67,6 +68,8 @@ export interface RuleNodeConfigurationDescriptor {
export interface IRuleNodeConfigurationComponent { export interface IRuleNodeConfigurationComponent {
ruleNodeId: string; ruleNodeId: string;
ruleChainId: string;
ruleChainType: RuleChainType;
configuration: RuleNodeConfiguration; configuration: RuleNodeConfiguration;
configurationChanged: Observable<RuleNodeConfiguration>; configurationChanged: Observable<RuleNodeConfiguration>;
validate(); validate();
@ -74,11 +77,16 @@ export interface IRuleNodeConfigurationComponent {
} }
@Directive() @Directive()
// tslint:disable-next-line:directive-class-suffix
export abstract class RuleNodeConfigurationComponent extends PageComponent implements export abstract class RuleNodeConfigurationComponent extends PageComponent implements
IRuleNodeConfigurationComponent, OnInit, AfterViewInit { IRuleNodeConfigurationComponent, OnInit, AfterViewInit {
ruleNodeId: string; ruleNodeId: string;
ruleChainId: string;
ruleChainType: RuleChainType;
configurationValue: RuleNodeConfiguration; configurationValue: RuleNodeConfiguration;
private configurationSet = false; private configurationSet = false;
@ -184,7 +192,7 @@ export enum RuleNodeType {
TRANSFORMATION = 'TRANSFORMATION', TRANSFORMATION = 'TRANSFORMATION',
ACTION = 'ACTION', ACTION = 'ACTION',
EXTERNAL = 'EXTERNAL', EXTERNAL = 'EXTERNAL',
RULE_CHAIN = 'RULE_CHAIN', FLOW = 'FLOW',
UNKNOWN = 'UNKNOWN', UNKNOWN = 'UNKNOWN',
INPUT = 'INPUT' INPUT = 'INPUT'
} }
@ -195,7 +203,7 @@ export const ruleNodeTypesLibrary = [
RuleNodeType.TRANSFORMATION, RuleNodeType.TRANSFORMATION,
RuleNodeType.ACTION, RuleNodeType.ACTION,
RuleNodeType.EXTERNAL, RuleNodeType.EXTERNAL,
RuleNodeType.RULE_CHAIN, RuleNodeType.FLOW,
]; ];
export interface RuleNodeTypeDescriptor { export interface RuleNodeTypeDescriptor {
@ -260,12 +268,12 @@ export const ruleNodeTypeDescriptors = new Map<RuleNodeType, RuleNodeTypeDescrip
} }
], ],
[ [
RuleNodeType.RULE_CHAIN, RuleNodeType.FLOW,
{ {
value: RuleNodeType.RULE_CHAIN, value: RuleNodeType.FLOW,
name: 'rulenode.type-rule-chain', name: 'rulenode.type-flow',
details: 'rulenode.type-rule-chain-details', details: 'rulenode.type-flow-details',
nodeClass: 'tb-rule-chain-type', nodeClass: 'tb-flow-type',
icon: 'settings_ethernet' icon: 'settings_ethernet'
} }
], ],
@ -310,7 +318,6 @@ export interface FcRuleNode extends FcRuleNodeType {
additionalInfo?: any; additionalInfo?: any;
configuration?: RuleNodeConfiguration; configuration?: RuleNodeConfiguration;
debugMode?: boolean; debugMode?: boolean;
targetRuleChainId?: string;
error?: string; error?: string;
highlighted?: boolean; highlighted?: boolean;
componentClazz?: string; componentClazz?: string;

2
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2617,6 +2617,8 @@
"type-external-details": "Interacts with external system", "type-external-details": "Interacts with external system",
"type-rule-chain": "Rule Chain", "type-rule-chain": "Rule Chain",
"type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
"type-flow": "Flow",
"type-flow-details": "Organizes message flow",
"type-input": "Input", "type-input": "Input",
"type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node", "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node",
"type-unknown": "Unknown", "type-unknown": "Unknown",

Loading…
Cancel
Save