Commit ddf8a3569e7157083c60fafa77f6eb3747cdd238

Authored by Vladyslav_Prykhodko
1 parent f3a5cb31

Refactoring validation mqtt topic filter; Add help hint

@@ -19,7 +19,6 @@ @@ -19,7 +19,6 @@
19 <section formGroupName="configuration"> 19 <section formGroupName="configuration">
20 <fieldset class="fields-group"> 20 <fieldset class="fields-group">
21 <legend class="group-title" translate>device-profile.mqtt-device-topic-filters</legend> 21 <legend class="group-title" translate>device-profile.mqtt-device-topic-filters</legend>
22 - <h6 class="mat-body" innerHTML="{{ 'device-profile.support-level-wildcards' | translate }}"></h6>  
23 <div fxLayoutGap="8px" fxLayout="column"> 22 <div fxLayoutGap="8px" fxLayout="column">
24 <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column"> 23 <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column">
25 <mat-form-field fxFlex> 24 <mat-form-field fxFlex>
@@ -30,8 +29,11 @@ @@ -30,8 +29,11 @@
30 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceTelemetryTopic').hasError('required')"> 29 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceTelemetryTopic').hasError('required')">
31 {{ 'device-profile.telemetry-topic-filter-required' | translate}} 30 {{ 'device-profile.telemetry-topic-filter-required' | translate}}
32 </mat-error> 31 </mat-error>
33 - <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceTelemetryTopic').hasError('pattern')">  
34 - {{ 'device-profile.not-valid-pattern-topic-filter' | translate}} 32 + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceTelemetryTopic').hasError('invalidSingleTopicCharacter')">
  33 + {{ 'device-profile.not-valid-single-character' | translate}}
  34 + </mat-error>
  35 + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceTelemetryTopic').hasError('invalidMultiTopicCharacter')">
  36 + {{ 'device-profile.not-valid-multi-character' | translate}}
35 </mat-error> 37 </mat-error>
36 </mat-form-field> 38 </mat-form-field>
37 <mat-form-field fxFlex> 39 <mat-form-field fxFlex>
@@ -42,8 +44,11 @@ @@ -42,8 +44,11 @@
42 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcRequestTopic').hasError('required')"> 44 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcRequestTopic').hasError('required')">
43 {{ 'device-profile.rpc-request-topic-filter-required' | translate}} 45 {{ 'device-profile.rpc-request-topic-filter-required' | translate}}
44 </mat-error> 46 </mat-error>
45 - <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcRequestTopic').hasError('pattern')">  
46 - {{ 'device-profile.not-valid-pattern-topic-filter' | translate}} 47 + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcRequestTopic').hasError('invalidSingleTopicCharacter')">
  48 + {{ 'device-profile.not-valid-single-character' | translate}}
  49 + </mat-error>
  50 + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcRequestTopic').hasError('invalidMultiTopicCharacter')">
  51 + {{ 'device-profile.not-valid-multi-character' | translate}}
47 </mat-error> 52 </mat-error>
48 </mat-form-field> 53 </mat-form-field>
49 </div> 54 </div>
@@ -56,8 +61,11 @@ @@ -56,8 +61,11 @@
56 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceAttributesTopic').hasError('required')"> 61 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceAttributesTopic').hasError('required')">
57 {{ 'device-profile.attributes-topic-filter-required' | translate}} 62 {{ 'device-profile.attributes-topic-filter-required' | translate}}
58 </mat-error> 63 </mat-error>
59 - <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceAttributesTopic').hasError('pattern')">  
60 - {{ 'device-profile.not-valid-pattern-topic-filter' | translate}} 64 + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceAttributesTopic').hasError('invalidSingleTopicCharacter')">
  65 + {{ 'device-profile.not-valid-single-character' | translate}}
  66 + </mat-error>
  67 + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceAttributesTopic').hasError('invalidMultiTopicCharacter')">
  68 + {{ 'device-profile.not-valid-multi-character' | translate}}
61 </mat-error> 69 </mat-error>
62 </mat-form-field> 70 </mat-form-field>
63 <mat-form-field fxFlex> 71 <mat-form-field fxFlex>
@@ -68,11 +76,17 @@ @@ -68,11 +76,17 @@
68 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcResponseTopic').hasError('required')"> 76 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcResponseTopic').hasError('required')">
69 {{ 'device-profile.rpc-response-topic-filter-required' | translate}} 77 {{ 'device-profile.rpc-response-topic-filter-required' | translate}}
70 </mat-error> 78 </mat-error>
71 - <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcResponseTopic').hasError('pattern')">  
72 - {{ 'device-profile.not-valid-pattern-topic-filter' | translate}} 79 + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcResponseTopic').hasError('invalidSingleTopicCharacter')">
  80 + {{ 'device-profile.not-valid-single-character' | translate}}
  81 + </mat-error>
  82 + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcResponseTopic').hasError('invalidMultiTopicCharacter')">
  83 + {{ 'device-profile.not-valid-multi-character' | translate}}
73 </mat-error> 84 </mat-error>
74 </mat-form-field> 85 </mat-form-field>
75 </div> 86 </div>
  87 + <div class="tb-hint" innerHTML="{{ 'device-profile.support-level-wildcards' | translate }}"></div>
  88 + <div class="tb-hint" innerHTML="{{ 'device-profile.single-level-wildcards-hint' | translate }}"></div>
  89 + <div class="tb-hint" innerHTML="{{ 'device-profile.multi-level-wildcards-hint' | translate }}"></div>
76 </div> 90 </div>
77 </fieldset> 91 </fieldset>
78 </section> 92 </section>
@@ -23,5 +23,9 @@ @@ -23,5 +23,9 @@
23 legend { 23 legend {
24 color: rgba(0, 0, 0, .7); 24 color: rgba(0, 0, 0, .7);
25 } 25 }
  26 +
  27 + .tb-hint{
  28 + padding: 0;
  29 + }
26 } 30 }
27 } 31 }
@@ -15,15 +15,24 @@ @@ -15,15 +15,24 @@
15 /// 15 ///
16 16
17 import { Component, forwardRef, Input, OnInit } from '@angular/core'; 17 import { Component, forwardRef, Input, OnInit } from '@angular/core';
18 -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; 18 +import {
  19 + ControlValueAccessor,
  20 + FormBuilder,
  21 + FormControl,
  22 + FormGroup,
  23 + NG_VALUE_ACCESSOR,
  24 + ValidatorFn,
  25 + Validators
  26 +} from '@angular/forms';
19 import { Store } from '@ngrx/store'; 27 import { Store } from '@ngrx/store';
20 import { AppState } from '@app/core/core.state'; 28 import { AppState } from '@app/core/core.state';
21 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 29 import { coerceBooleanProperty } from '@angular/cdk/coercion';
22 import { 30 import {
23 DeviceProfileTransportConfiguration, 31 DeviceProfileTransportConfiguration,
24 - DeviceTransportType, MqttDeviceProfileTransportConfiguration 32 + DeviceTransportType,
  33 + MqttDeviceProfileTransportConfiguration
25 } from '@shared/models/device.models'; 34 } from '@shared/models/device.models';
26 -import { isDefinedAndNotNull } from '../../../../../core/utils'; 35 +import { isDefinedAndNotNull } from '@core/utils';
27 36
28 @Component({ 37 @Component({
29 selector: 'tb-mqtt-device-profile-transport-configuration', 38 selector: 'tb-mqtt-device-profile-transport-configuration',
@@ -41,11 +50,10 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control @@ -41,11 +50,10 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
41 50
42 private requiredValue: boolean; 51 private requiredValue: boolean;
43 52
44 - private MQTTTopicPattern = new RegExp('^((?![#+]).)*$|^(.*[^#]\/|^)#$|^(.*\/|^)\+(\/.*|$)$');  
45 -  
46 get required(): boolean { 53 get required(): boolean {
47 return this.requiredValue; 54 return this.requiredValue;
48 } 55 }
  56 +
49 @Input() 57 @Input()
50 set required(value: boolean) { 58 set required(value: boolean) {
51 this.requiredValue = coerceBooleanProperty(value); 59 this.requiredValue = coerceBooleanProperty(value);
@@ -70,10 +78,10 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control @@ -70,10 +78,10 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
70 ngOnInit() { 78 ngOnInit() {
71 this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({ 79 this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({
72 configuration: this.fb.group({ 80 configuration: this.fb.group({
73 - deviceAttributesTopic: [null, [Validators.required, Validators.pattern(this.MQTTTopicPattern)]],  
74 - deviceTelemetryTopic: [null, [Validators.required, Validators.pattern(this.MQTTTopicPattern)]],  
75 - deviceRpcRequestTopic: [null, [Validators.required, Validators.pattern(this.MQTTTopicPattern)]],  
76 - deviceRpcResponseTopic: [null, [Validators.required, Validators.pattern(this.MQTTTopicPattern)]] 81 + deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]],
  82 + deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]],
  83 + deviceRpcRequestTopic: [null, [Validators.required, this.validationMQTTTopic()]],
  84 + deviceRpcResponseTopic: [null, [Validators.required, this.validationMQTTTopic()]]
77 }) 85 })
78 }); 86 });
79 this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { 87 this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => {
@@ -104,4 +112,34 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control @@ -104,4 +112,34 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
104 } 112 }
105 this.propagateChange(configuration); 113 this.propagateChange(configuration);
106 } 114 }
  115 +
  116 + private validationMQTTTopic(): ValidatorFn {
  117 + return (c: FormControl) => {
  118 + const newTopic = c.value;
  119 + const wildcardSymbols = /[#+]/g;
  120 + let findSymbol = wildcardSymbols.exec(newTopic);
  121 + while (findSymbol) {
  122 + const index = findSymbol.index;
  123 + const currentSymbol = findSymbol[0];
  124 + const prevSymbol = index > 0 ? newTopic[index - 1] : null;
  125 + const nextSymbol = index < (newTopic.length - 1) ? newTopic[index + 1] : null;
  126 + if (currentSymbol === '#' && (index !== (newTopic.length - 1) || (prevSymbol !== null && prevSymbol !== '/'))) {
  127 + return {
  128 + invalidMultiTopicCharacter: {
  129 + valid: false
  130 + }
  131 + };
  132 + }
  133 + if (currentSymbol === '+' && ((prevSymbol !== null && prevSymbol !== '/') || (nextSymbol !== null && nextSymbol !== '/'))) {
  134 + return {
  135 + invalidSingleTopicCharacter: {
  136 + valid: false
  137 + }
  138 + };
  139 + }
  140 + findSymbol = wildcardSymbols.exec(newTopic);
  141 + }
  142 + return null;
  143 + };
  144 + }
107 } 145 }
@@ -792,7 +792,7 @@ @@ -792,7 +792,7 @@
792 "no-device-profiles-found": "No device profiles found.", 792 "no-device-profiles-found": "No device profiles found.",
793 "create-new-device-profile": "Create a new one!", 793 "create-new-device-profile": "Create a new one!",
794 "mqtt-device-topic-filters": "MQTT device topic filters", 794 "mqtt-device-topic-filters": "MQTT device topic filters",
795 - "support-level-wildcards": "Support single <code>(+)</code> and multi <code>(#)</code> level wildcards", 795 + "support-level-wildcards": "Single <code>[+]</code> and multi-level <code>[#]</code> wildcards supported.",
796 "telemetry-topic-filter": "Telemetry topic filter", 796 "telemetry-topic-filter": "Telemetry topic filter",
797 "telemetry-topic-filter-required": "Telemetry topic filter is required.", 797 "telemetry-topic-filter-required": "Telemetry topic filter is required.",
798 "rpc-request-topic-filter": "RPC request topic filter", 798 "rpc-request-topic-filter": "RPC request topic filter",
@@ -801,7 +801,10 @@ @@ -801,7 +801,10 @@
801 "attributes-topic-filter-required": "Attributes topic filter is required.", 801 "attributes-topic-filter-required": "Attributes topic filter is required.",
802 "rpc-response-topic-filter": "RPC response topic filter", 802 "rpc-response-topic-filter": "RPC response topic filter",
803 "rpc-response-topic-filter-required": "RPC response topic filter is required.", 803 "rpc-response-topic-filter-required": "RPC response topic filter is required.",
804 - "not-valid-pattern-topic-filter": "Not valid pattern topic filter" 804 + "not-valid-single-character": "Invalid use of a single-level wildcard character",
  805 + "not-valid-multi-character": "Invalid use of a multi-level wildcard character",
  806 + "single-level-wildcards-hint": "<code>[+]</code> is suitable for any topic level. Ex.: <b>v1/devices/+/telemetry</b> or <b>+/devices/+/attributes</b>.",
  807 + "multi-level-wildcards-hint": "<code>[#]</code> can replace the topic filter itself and must be the last symbol of the topic. Ex.: <b>#</b> or <b>v1/devices/me/#</b>."
805 }, 808 },
806 "dialog": { 809 "dialog": {
807 "close": "Close dialog" 810 "close": "Close dialog"