/// /// Copyright © 2016-2025 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// import { Component } from '@angular/core'; import { FormGroup, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; import { AttributeScope, telemetryTypeTranslations } from '@app/shared/models/telemetry/telemetry.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { maxDeduplicateTimeSecs, ProcessingSettings, ProcessingSettingsForm, ProcessingType, ProcessingTypeTranslationMap } from '@home/components/rule-node/action/timeseries-config.models'; import { AttributeNodeConfiguration, AttributeNodeConfigurationForm, defaultAttributeAdvancedProcessingStrategy } from '@home/components/rule-node/action/attributes-config.model'; @Component({ selector: 'tb-action-node-attributes-config', templateUrl: './attributes-config.component.html', styleUrls: [] }) export class AttributesConfigComponent extends RuleNodeConfigurationComponent { attributeScopeMap = AttributeScope; attributeScopes = Object.keys(AttributeScope); telemetryTypeTranslationsMap = telemetryTypeTranslations; ProcessingType = ProcessingType; processingStrategies = [ProcessingType.ON_EVERY_MESSAGE, ProcessingType.DEDUPLICATE, ProcessingType.WEBSOCKETS_ONLY]; ProcessingTypeTranslationMap = ProcessingTypeTranslationMap; maxDeduplicateTime = maxDeduplicateTimeSecs; attributesConfigForm: UntypedFormGroup; constructor(private fb: UntypedFormBuilder) { super(); } protected configForm(): UntypedFormGroup { return this.attributesConfigForm; } protected validatorTriggers(): string[] { return ['processingSettings.isAdvanced', 'processingSettings.type']; } protected prepareInputConfig(config: AttributeNodeConfiguration): AttributeNodeConfigurationForm { let processingSettings: ProcessingSettingsForm; if (config?.processingSettings) { const isAdvanced = config?.processingSettings?.type === ProcessingType.ADVANCED; processingSettings = { type: isAdvanced ? ProcessingType.ON_EVERY_MESSAGE : config.processingSettings.type, isAdvanced: isAdvanced, deduplicationIntervalSecs: config.processingSettings?.deduplicationIntervalSecs ?? 60, advanced: isAdvanced ? config.processingSettings : defaultAttributeAdvancedProcessingStrategy } } else { processingSettings = { type: ProcessingType.ON_EVERY_MESSAGE, isAdvanced: false, deduplicationIntervalSecs: 60, advanced: defaultAttributeAdvancedProcessingStrategy }; } return { ...config, processingSettings: processingSettings } } protected prepareOutputConfig(config: AttributeNodeConfigurationForm): AttributeNodeConfiguration { let processingSettings: ProcessingSettings; if (config.processingSettings.isAdvanced) { processingSettings = { ...config.processingSettings.advanced, type: ProcessingType.ADVANCED }; } else { processingSettings = { type: config.processingSettings.type, deduplicationIntervalSecs: config.processingSettings?.deduplicationIntervalSecs }; } return { ...config, processingSettings }; } protected onConfigurationSet(configuration: RuleNodeConfiguration) { this.attributesConfigForm = this.fb.group({ processingSettings: this.fb.group({ isAdvanced: [configuration?.processingSettings?.isAdvanced ?? false], type: [configuration?.processingSettings?.type ?? ProcessingType.ON_EVERY_MESSAGE], deduplicationIntervalSecs: [ {value: configuration?.processingSettings?.deduplicationIntervalSecs ?? 60, disabled: true}, [Validators.required, Validators.max(maxDeduplicateTimeSecs)] ], advanced: [{value: null, disabled: true}] }), scope: [configuration ? configuration.scope : null, [Validators.required]], notifyDevice: [configuration ? configuration.notifyDevice : true, []], sendAttributesUpdatedNotification: [configuration ? configuration.sendAttributesUpdatedNotification : false, []], updateAttributesOnlyOnValueChange: [configuration ? configuration.updateAttributesOnlyOnValueChange : false, []] }); this.attributesConfigForm.get('scope').valueChanges.pipe( takeUntilDestroyed(this.destroyRef) ).subscribe((value) => { if (value !== AttributeScope.SHARED_SCOPE) { this.attributesConfigForm.get('notifyDevice').patchValue(false, {emitEvent: false}); } if (value === AttributeScope.CLIENT_SCOPE) { this.attributesConfigForm.get('sendAttributesUpdatedNotification').patchValue(false, {emitEvent: false}); } this.attributesConfigForm.get('updateAttributesOnlyOnValueChange').patchValue(false, {emitEvent: false}); }); } protected updateValidators(emitEvent: boolean, _trigger?: string) { const processingForm = this.attributesConfigForm.get('processingSettings') as FormGroup; const isAdvanced: boolean = processingForm.get('isAdvanced').value; const type: ProcessingType = processingForm.get('type').value; if (!isAdvanced && type === ProcessingType.DEDUPLICATE) { processingForm.get('deduplicationIntervalSecs').enable({emitEvent}); } else { processingForm.get('deduplicationIntervalSecs').disable({emitEvent}); } if (isAdvanced) { processingForm.get('advanced').enable({emitEvent}); } else { processingForm.get('advanced').disable({emitEvent}); } } }