Refactoring validation mqtt topic filter; Add help hint

This commit is contained in:
Vladyslav_Prykhodko 2020-09-09 16:08:45 +03:00
parent f3a5cb3162
commit ddf8a3569e
4 changed files with 79 additions and 20 deletions

View File

@ -19,7 +19,6 @@
<section formGroupName="configuration">
<fieldset class="fields-group">
<legend class="group-title" translate>device-profile.mqtt-device-topic-filters</legend>
<h6 class="mat-body" innerHTML="{{ 'device-profile.support-level-wildcards' | translate }}"></h6>
<div fxLayoutGap="8px" fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column">
<mat-form-field fxFlex>
@ -30,8 +29,11 @@
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceTelemetryTopic').hasError('required')">
{{ 'device-profile.telemetry-topic-filter-required' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceTelemetryTopic').hasError('pattern')">
{{ 'device-profile.not-valid-pattern-topic-filter' | translate}}
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceTelemetryTopic').hasError('invalidSingleTopicCharacter')">
{{ 'device-profile.not-valid-single-character' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceTelemetryTopic').hasError('invalidMultiTopicCharacter')">
{{ 'device-profile.not-valid-multi-character' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex>
@ -42,8 +44,11 @@
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcRequestTopic').hasError('required')">
{{ 'device-profile.rpc-request-topic-filter-required' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcRequestTopic').hasError('pattern')">
{{ 'device-profile.not-valid-pattern-topic-filter' | translate}}
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcRequestTopic').hasError('invalidSingleTopicCharacter')">
{{ 'device-profile.not-valid-single-character' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcRequestTopic').hasError('invalidMultiTopicCharacter')">
{{ 'device-profile.not-valid-multi-character' | translate}}
</mat-error>
</mat-form-field>
</div>
@ -56,8 +61,11 @@
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceAttributesTopic').hasError('required')">
{{ 'device-profile.attributes-topic-filter-required' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceAttributesTopic').hasError('pattern')">
{{ 'device-profile.not-valid-pattern-topic-filter' | translate}}
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceAttributesTopic').hasError('invalidSingleTopicCharacter')">
{{ 'device-profile.not-valid-single-character' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceAttributesTopic').hasError('invalidMultiTopicCharacter')">
{{ 'device-profile.not-valid-multi-character' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex>
@ -68,11 +76,17 @@
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcResponseTopic').hasError('required')">
{{ 'device-profile.rpc-response-topic-filter-required' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcResponseTopic').hasError('pattern')">
{{ 'device-profile.not-valid-pattern-topic-filter' | translate}}
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcResponseTopic').hasError('invalidSingleTopicCharacter')">
{{ 'device-profile.not-valid-single-character' | translate}}
</mat-error>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.deviceRpcResponseTopic').hasError('invalidMultiTopicCharacter')">
{{ 'device-profile.not-valid-multi-character' | translate}}
</mat-error>
</mat-form-field>
</div>
<div class="tb-hint" innerHTML="{{ 'device-profile.support-level-wildcards' | translate }}"></div>
<div class="tb-hint" innerHTML="{{ 'device-profile.single-level-wildcards-hint' | translate }}"></div>
<div class="tb-hint" innerHTML="{{ 'device-profile.multi-level-wildcards-hint' | translate }}"></div>
</div>
</fieldset>
</section>

View File

@ -23,5 +23,9 @@
legend {
color: rgba(0, 0, 0, .7);
}
.tb-hint{
padding: 0;
}
}
}

View File

@ -15,15 +15,24 @@
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup,
NG_VALUE_ACCESSOR,
ValidatorFn,
Validators
} from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {
DeviceProfileTransportConfiguration,
DeviceTransportType, MqttDeviceProfileTransportConfiguration
DeviceTransportType,
MqttDeviceProfileTransportConfiguration
} from '@shared/models/device.models';
import { isDefinedAndNotNull } from '../../../../../core/utils';
import { isDefinedAndNotNull } from '@core/utils';
@Component({
selector: 'tb-mqtt-device-profile-transport-configuration',
@ -41,11 +50,10 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
private requiredValue: boolean;
private MQTTTopicPattern = new RegExp('^((?![#+]).)*$|^(.*[^#]\/|^)#$|^(.*\/|^)\+(\/.*|$)$');
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
@ -70,10 +78,10 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
ngOnInit() {
this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({
configuration: this.fb.group({
deviceAttributesTopic: [null, [Validators.required, Validators.pattern(this.MQTTTopicPattern)]],
deviceTelemetryTopic: [null, [Validators.required, Validators.pattern(this.MQTTTopicPattern)]],
deviceRpcRequestTopic: [null, [Validators.required, Validators.pattern(this.MQTTTopicPattern)]],
deviceRpcResponseTopic: [null, [Validators.required, Validators.pattern(this.MQTTTopicPattern)]]
deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]],
deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]],
deviceRpcRequestTopic: [null, [Validators.required, this.validationMQTTTopic()]],
deviceRpcResponseTopic: [null, [Validators.required, this.validationMQTTTopic()]]
})
});
this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => {
@ -104,4 +112,34 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
}
this.propagateChange(configuration);
}
private validationMQTTTopic(): ValidatorFn {
return (c: FormControl) => {
const newTopic = c.value;
const wildcardSymbols = /[#+]/g;
let findSymbol = wildcardSymbols.exec(newTopic);
while (findSymbol) {
const index = findSymbol.index;
const currentSymbol = findSymbol[0];
const prevSymbol = index > 0 ? newTopic[index - 1] : null;
const nextSymbol = index < (newTopic.length - 1) ? newTopic[index + 1] : null;
if (currentSymbol === '#' && (index !== (newTopic.length - 1) || (prevSymbol !== null && prevSymbol !== '/'))) {
return {
invalidMultiTopicCharacter: {
valid: false
}
};
}
if (currentSymbol === '+' && ((prevSymbol !== null && prevSymbol !== '/') || (nextSymbol !== null && nextSymbol !== '/'))) {
return {
invalidSingleTopicCharacter: {
valid: false
}
};
}
findSymbol = wildcardSymbols.exec(newTopic);
}
return null;
};
}
}

View File

@ -792,7 +792,7 @@
"no-device-profiles-found": "No device profiles found.",
"create-new-device-profile": "Create a new one!",
"mqtt-device-topic-filters": "MQTT device topic filters",
"support-level-wildcards": "Support single <code>(+)</code> and multi <code>(#)</code> level wildcards",
"support-level-wildcards": "Single <code>[+]</code> and multi-level <code>[#]</code> wildcards supported.",
"telemetry-topic-filter": "Telemetry topic filter",
"telemetry-topic-filter-required": "Telemetry topic filter is required.",
"rpc-request-topic-filter": "RPC request topic filter",
@ -801,7 +801,10 @@
"attributes-topic-filter-required": "Attributes topic filter is required.",
"rpc-response-topic-filter": "RPC response topic filter",
"rpc-response-topic-filter-required": "RPC response topic filter is required.",
"not-valid-pattern-topic-filter": "Not valid pattern topic filter"
"not-valid-single-character": "Invalid use of a single-level wildcard character",
"not-valid-multi-character": "Invalid use of a multi-level wildcard character",
"single-level-wildcards-hint": "<code>[+]</code> is suitable for any topic level. Ex.: <b>v1/devices/+/telemetry</b> or <b>+/devices/+/attributes</b>.",
"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>."
},
"dialog": {
"close": "Close dialog"