-
- {{ 'rule-node-config.skip-latest-persistence' | translate }}
-
+
+
diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.ts
index d480ec4d39..7f27bca241 100644
--- a/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.ts
+++ b/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.ts
@@ -1,5 +1,5 @@
///
-/// Copyright © 2016-2024 The Thingsboard Authors
+/// 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.
@@ -15,8 +15,18 @@
///
import { Component } from '@angular/core';
-import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
-import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { RuleNodeConfigurationComponent } from '@shared/models/rule-node.models';
+import {
+ defaultAdvancedProcessingStrategy,
+ maxDeduplicateTimeSecs,
+ ProcessingSettings,
+ ProcessingSettingsForm,
+ ProcessingType,
+ ProcessingTypeTranslationMap,
+ TimeseriesNodeConfiguration,
+ TimeseriesNodeConfigurationForm
+} from '@home/components/rule-node/action/timeseries-config.models';
@Component({
selector: 'tb-action-node-timeseries-config',
@@ -25,21 +35,98 @@ import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/m
})
export class TimeseriesConfigComponent extends RuleNodeConfigurationComponent {
- timeseriesConfigForm: UntypedFormGroup;
+ timeseriesConfigForm: FormGroup;
- constructor(private fb: UntypedFormBuilder) {
+ ProcessingType = ProcessingType;
+ processingStrategies = [ProcessingType.ON_EVERY_MESSAGE, ProcessingType.DEDUPLICATE, ProcessingType.WEBSOCKETS_ONLY];
+ ProcessingTypeTranslationMap = ProcessingTypeTranslationMap;
+
+ maxDeduplicateTime = maxDeduplicateTimeSecs
+
+ constructor(private fb: FormBuilder) {
super();
}
- protected configForm(): UntypedFormGroup {
+ protected configForm(): FormGroup {
return this.timeseriesConfigForm;
}
- protected onConfigurationSet(configuration: RuleNodeConfiguration) {
+ protected validatorTriggers(): string[] {
+ return ['processingSettings.isAdvanced', 'processingSettings.type'];
+ }
+
+ protected prepareInputConfig(config: TimeseriesNodeConfiguration): TimeseriesNodeConfigurationForm {
+ 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 : defaultAdvancedProcessingStrategy
+ }
+ } else {
+ processingSettings = {
+ type: ProcessingType.ON_EVERY_MESSAGE,
+ isAdvanced: false,
+ deduplicationIntervalSecs: 60,
+ advanced: defaultAdvancedProcessingStrategy
+ };
+ }
+ return {
+ ...config,
+ processingSettings: processingSettings
+ }
+ }
+
+ protected prepareOutputConfig(config: TimeseriesNodeConfigurationForm): TimeseriesNodeConfiguration {
+ 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(config: TimeseriesNodeConfigurationForm) {
this.timeseriesConfigForm = this.fb.group({
- defaultTTL: [configuration ? configuration.defaultTTL : null, [Validators.required, Validators.min(0)]],
- skipLatestPersistence: [configuration ? configuration.skipLatestPersistence : false, []],
- useServerTs: [configuration ? configuration.useServerTs : false, []]
+ processingSettings: this.fb.group({
+ isAdvanced: [config?.processingSettings?.isAdvanced ?? false],
+ type: [config?.processingSettings?.type ?? ProcessingType.ON_EVERY_MESSAGE],
+ deduplicationIntervalSecs: [
+ {value: config?.processingSettings?.deduplicationIntervalSecs ?? 60, disabled: true},
+ [Validators.required, Validators.max(maxDeduplicateTimeSecs)]
+ ],
+ advanced: [{value: null, disabled: true}]
+ }),
+ defaultTTL: [config?.defaultTTL ?? null, [Validators.required, Validators.min(0)]],
+ useServerTs: [config?.useServerTs ?? false]
});
}
+
+ protected updateValidators(emitEvent: boolean, _trigger?: string) {
+ const processingForm = this.timeseriesConfigForm.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});
+ }
+ }
}
diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.models.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.models.ts
new file mode 100644
index 0000000000..9785125999
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.models.ts
@@ -0,0 +1,80 @@
+///
+/// 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 { DAY, SECOND } from '@shared/models/time/time.models';
+
+export const maxDeduplicateTimeSecs = DAY / SECOND;
+
+export interface TimeseriesNodeConfiguration {
+ processingSettings: ProcessingSettings;
+ defaultTTL: number;
+ useServerTs: boolean;
+}
+
+export interface TimeseriesNodeConfigurationForm extends Omit
{
+ processingSettings: ProcessingSettingsForm
+}
+
+export type ProcessingSettings = BasicProcessingSettings & Partial & Partial;
+
+export type ProcessingSettingsForm = Omit & {
+ isAdvanced: boolean;
+ advanced?: Partial;
+ type: ProcessingType;
+};
+
+export enum ProcessingType {
+ ON_EVERY_MESSAGE = 'ON_EVERY_MESSAGE',
+ DEDUPLICATE = 'DEDUPLICATE',
+ WEBSOCKETS_ONLY = 'WEBSOCKETS_ONLY',
+ ADVANCED = 'ADVANCED',
+ SKIP = 'SKIP'
+}
+
+export const ProcessingTypeTranslationMap = new Map([
+ [ProcessingType.ON_EVERY_MESSAGE, 'rule-node-config.save-time-series.strategy-type.every-message'],
+ [ProcessingType.DEDUPLICATE, 'rule-node-config.save-time-series.strategy-type.deduplicate'],
+ [ProcessingType.WEBSOCKETS_ONLY, 'rule-node-config.save-time-series.strategy-type.web-sockets-only'],
+ [ProcessingType.SKIP, 'rule-node-config.save-time-series.strategy-type.skip'],
+])
+
+export interface BasicProcessingSettings {
+ type: ProcessingType;
+}
+
+export interface DeduplicateProcessingStrategy extends BasicProcessingSettings {
+ deduplicationIntervalSecs: number;
+}
+
+export interface AdvancedProcessingStrategy extends BasicProcessingSettings {
+ timeseries: AdvancedProcessingConfig;
+ latest: AdvancedProcessingConfig;
+ webSockets: AdvancedProcessingConfig;
+ calculatedFields: AdvancedProcessingConfig;
+}
+
+export type AdvancedProcessingConfig = WithOptional;
+
+export const defaultAdvancedProcessingConfig: AdvancedProcessingConfig = {
+ type: ProcessingType.ON_EVERY_MESSAGE
+}
+
+export const defaultAdvancedProcessingStrategy: Omit = {
+ timeseries: defaultAdvancedProcessingConfig,
+ latest: defaultAdvancedProcessingConfig,
+ webSockets: defaultAdvancedProcessingConfig,
+ calculatedFields: defaultAdvancedProcessingConfig,
+}
diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.html
index 4b2df342ab..ffa18da35d 100644
--- a/ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.html
+++ b/ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.html
@@ -1,6 +1,6 @@
-