Commit ddf8a3569e7157083c60fafa77f6eb3747cdd238

Authored by Vladyslav_Prykhodko
1 parent f3a5cb31

Refactoring validation mqtt topic filter; Add help hint

... ... @@ -19,7 +19,6 @@
19 19 <section formGroupName="configuration">
20 20 <fieldset class="fields-group">
21 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 22 <div fxLayoutGap="8px" fxLayout="column">
24 23 <div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column">
25 24 <mat-form-field fxFlex>
... ... @@ -30,8 +29,11 @@
30 29 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceTelemetryTopic').hasError('required')">
31 30 {{ 'device-profile.telemetry-topic-filter-required' | translate}}
32 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 37 </mat-error>
36 38 </mat-form-field>
37 39 <mat-form-field fxFlex>
... ... @@ -42,8 +44,11 @@
42 44 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcRequestTopic').hasError('required')">
43 45 {{ 'device-profile.rpc-request-topic-filter-required' | translate}}
44 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 52 </mat-error>
48 53 </mat-form-field>
49 54 </div>
... ... @@ -56,8 +61,11 @@
56 61 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceAttributesTopic').hasError('required')">
57 62 {{ 'device-profile.attributes-topic-filter-required' | translate}}
58 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 69 </mat-error>
62 70 </mat-form-field>
63 71 <mat-form-field fxFlex>
... ... @@ -68,11 +76,17 @@
68 76 <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcResponseTopic').hasError('required')">
69 77 {{ 'device-profile.rpc-response-topic-filter-required' | translate}}
70 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 84 </mat-error>
74 85 </mat-form-field>
75 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 90 </div>
77 91 </fieldset>
78 92 </section>
... ...
... ... @@ -23,5 +23,9 @@
23 23 legend {
24 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 ///
16 16
17 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 27 import { Store } from '@ngrx/store';
20 28 import { AppState } from '@app/core/core.state';
21 29 import { coerceBooleanProperty } from '@angular/cdk/coercion';
22 30 import {
23 31 DeviceProfileTransportConfiguration,
24   - DeviceTransportType, MqttDeviceProfileTransportConfiguration
  32 + DeviceTransportType,
  33 + MqttDeviceProfileTransportConfiguration
25 34 } from '@shared/models/device.models';
26   -import { isDefinedAndNotNull } from '../../../../../core/utils';
  35 +import { isDefinedAndNotNull } from '@core/utils';
27 36
28 37 @Component({
29 38 selector: 'tb-mqtt-device-profile-transport-configuration',
... ... @@ -41,11 +50,10 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
41 50
42 51 private requiredValue: boolean;
43 52
44   - private MQTTTopicPattern = new RegExp('^((?![#+]).)*$|^(.*[^#]\/|^)#$|^(.*\/|^)\+(\/.*|$)$');
45   -
46 53 get required(): boolean {
47 54 return this.requiredValue;
48 55 }
  56 +
49 57 @Input()
50 58 set required(value: boolean) {
51 59 this.requiredValue = coerceBooleanProperty(value);
... ... @@ -70,10 +78,10 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
70 78 ngOnInit() {
71 79 this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({
72 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 87 this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => {
... ... @@ -104,4 +112,34 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
104 112 }
105 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 792 "no-device-profiles-found": "No device profiles found.",
793 793 "create-new-device-profile": "Create a new one!",
794 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 796 "telemetry-topic-filter": "Telemetry topic filter",
797 797 "telemetry-topic-filter-required": "Telemetry topic filter is required.",
798 798 "rpc-request-topic-filter": "RPC request topic filter",
... ... @@ -801,7 +801,10 @@
801 801 "attributes-topic-filter-required": "Attributes topic filter is required.",
802 802 "rpc-response-topic-filter": "RPC response topic filter",
803 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 809 "dialog": {
807 810 "close": "Close dialog"
... ...