UI: [Rule node save ts] Rename Persistence settings to Processing settings

This commit is contained in:
Vladyslav_Prykhodko 2025-02-07 12:15:32 +02:00
parent 12a8c070a6
commit fc3bc52247
11 changed files with 100 additions and 124 deletions

View File

@ -24,11 +24,11 @@ import {
Validator Validator
} from '@angular/forms'; } from '@angular/forms';
import { import {
AdvancedPersistenceConfig, AdvancedProcessingConfig,
defaultAdvancedPersistenceConfig, defaultAdvancedProcessingConfig,
maxDeduplicateTimeSecs, maxDeduplicateTimeSecs,
PersistenceType, ProcessingType,
PersistenceTypeTranslationMap ProcessingTypeTranslationMap
} from '@home/components/rule-node/action/timeseries-config.models'; } from '@home/components/rule-node/action/timeseries-config.models';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@ -52,13 +52,13 @@ export class AdvancedPersistenceSettingRowComponent implements ControlValueAcces
title: string; title: string;
persistenceSettingRowForm = this.fb.group({ persistenceSettingRowForm = this.fb.group({
type: [defaultAdvancedPersistenceConfig.type], type: [defaultAdvancedProcessingConfig.type],
deduplicationIntervalSecs: [{value: 60, disabled: true}] deduplicationIntervalSecs: [{value: 60, disabled: true}]
}); });
PersistenceType = PersistenceType; PersistenceType = ProcessingType;
persistenceStrategies = [PersistenceType.ON_EVERY_MESSAGE, PersistenceType.DEDUPLICATE, PersistenceType.SKIP]; persistenceStrategies = [ProcessingType.ON_EVERY_MESSAGE, ProcessingType.DEDUPLICATE, ProcessingType.SKIP];
PersistenceTypeTranslationMap = PersistenceTypeTranslationMap; PersistenceTypeTranslationMap = ProcessingTypeTranslationMap;
maxDeduplicateTime = maxDeduplicateTimeSecs; maxDeduplicateTime = maxDeduplicateTimeSecs;
@ -96,16 +96,16 @@ export class AdvancedPersistenceSettingRowComponent implements ControlValueAcces
}; };
} }
writeValue(value: AdvancedPersistenceConfig) { writeValue(value: AdvancedProcessingConfig) {
if (isDefinedAndNotNull(value)) { if (isDefinedAndNotNull(value)) {
this.persistenceSettingRowForm.patchValue(value, {emitEvent: false}); this.persistenceSettingRowForm.patchValue(value, {emitEvent: false});
} else { } else {
this.persistenceSettingRowForm.patchValue(defaultAdvancedPersistenceConfig); this.persistenceSettingRowForm.patchValue(defaultAdvancedProcessingConfig);
} }
} }
private updatedValidation() { private updatedValidation() {
if (this.persistenceSettingRowForm.get('type').value === PersistenceType.DEDUPLICATE) { if (this.persistenceSettingRowForm.get('type').value === ProcessingType.DEDUPLICATE) {
this.persistenceSettingRowForm.get('deduplicationIntervalSecs').enable({emitEvent: false}); this.persistenceSettingRowForm.get('deduplicationIntervalSecs').enable({emitEvent: false});
} else { } else {
this.persistenceSettingRowForm.get('deduplicationIntervalSecs').disable({emitEvent: false}) this.persistenceSettingRowForm.get('deduplicationIntervalSecs').disable({emitEvent: false})

View File

@ -16,6 +16,11 @@
--> -->
<section [formGroup]="persistenceForm" class="tb-form-panel no-border no-padding"> <section [formGroup]="persistenceForm" class="tb-form-panel no-border no-padding">
<tb-example-hint
[hintText]="'rule-node-config.save-time-series.advanced-settings-hint'"
[popupHelpLink]="'rulenode/save_timeseries_node_advanced'"
>
</tb-example-hint>
<tb-advanced-persistence-setting-row <tb-advanced-persistence-setting-row
formControlName="timeseries" formControlName="timeseries"
title="{{ 'rule-node-config.save-time-series.time-series' | translate }}" title="{{ 'rule-node-config.save-time-series.time-series' | translate }}"

View File

@ -24,7 +24,7 @@ import {
} from '@angular/forms'; } from '@angular/forms';
import { Component, forwardRef } from '@angular/core'; import { Component, forwardRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { AdvancedPersistenceStrategy } from '@home/components/rule-node/action/timeseries-config.models'; import { AdvancedProcessingStrategy } from '@home/components/rule-node/action/timeseries-config.models';
@Component({ @Component({
selector: 'tb-advanced-persistence-settings', selector: 'tb-advanced-persistence-settings',
@ -76,8 +76,7 @@ export class AdvancedPersistenceSettingComponent implements ControlValueAccessor
}; };
} }
writeValue(value: AdvancedPersistenceStrategy) { writeValue(value: AdvancedProcessingStrategy) {
this.persistenceForm.patchValue(value, {emitEvent: false}); this.persistenceForm.patchValue(value, {emitEvent: false});
} }
} }

View File

@ -16,10 +16,10 @@
--> -->
<section [formGroup]="timeseriesConfigForm" class="tb-form-panel no-border no-padding"> <section [formGroup]="timeseriesConfigForm" class="tb-form-panel no-border no-padding">
<div class="tb-form-panel stroked no-padding-bottom no-gap" formGroupName="persistenceSettings"> <div class="tb-form-panel stroked no-padding-bottom no-gap" formGroupName="processingSettings">
<div class="mb-4 flex flex-row items-center justify-between"> <div class="mb-4 flex flex-row items-center justify-between">
<div class="tb-form-panel-title" tb-hint-tooltip-icon="{{ 'rule-node-config.save-time-series.persistence-settings-hint' | translate}}" translate> <div class="tb-form-panel-title" tb-hint-tooltip-icon="{{ 'rule-node-config.save-time-series.processing-settings-hint' | translate}}" translate>
rule-node-config.save-time-series.persistence-settings rule-node-config.save-time-series.processing-settings
</div> </div>
<tb-toggle-select appearance="fill" selectMediaBreakpoint="xs" <tb-toggle-select appearance="fill" selectMediaBreakpoint="xs"
formControlName="isAdvanced"> formControlName="isAdvanced">
@ -27,7 +27,7 @@
<tb-toggle-option [value]=true>{{ 'rule-node-config.advanced-mode' | translate }}</tb-toggle-option> <tb-toggle-option [value]=true>{{ 'rule-node-config.advanced-mode' | translate }}</tb-toggle-option>
</tb-toggle-select> </tb-toggle-select>
</div> </div>
@if(!timeseriesConfigForm.get('persistenceSettings.isAdvanced').value) { @if(!timeseriesConfigForm.get('processingSettings.isAdvanced').value) {
<mat-form-field> <mat-form-field>
<mat-label translate>rule-node-config.save-time-series.strategy</mat-label> <mat-label translate>rule-node-config.save-time-series.strategy</mat-label>
<mat-select formControlName="type"> <mat-select formControlName="type">
@ -37,7 +37,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
@if(timeseriesConfigForm.get('persistenceSettings.type').value === PersistenceType.DEDUPLICATE) { @if(timeseriesConfigForm.get('processingSettings.type').value === PersistenceType.DEDUPLICATE) {
<tb-time-unit-input <tb-time-unit-input
required required
labelText="{{ 'rule-node-config.save-time-series.deduplication-interval' | translate }}" labelText="{{ 'rule-node-config.save-time-series.deduplication-interval' | translate }}"
@ -49,8 +49,7 @@
formControlName="deduplicationIntervalSecs"> formControlName="deduplicationIntervalSecs">
</tb-time-unit-input> </tb-time-unit-input>
} }
} } @else {
@else {
<tb-advanced-persistence-settings <tb-advanced-persistence-settings
class="mb-4" class="mb-4"
formControlName="advanced" formControlName="advanced"

View File

@ -20,10 +20,10 @@ import { RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'
import { import {
defaultAdvancedPersistenceStrategy, defaultAdvancedPersistenceStrategy,
maxDeduplicateTimeSecs, maxDeduplicateTimeSecs,
PersistenceSettings, ProcessingSettings,
PersistenceSettingsForm, ProcessingSettingsForm,
PersistenceType, ProcessingType,
PersistenceTypeTranslationMap, ProcessingTypeTranslationMap,
TimeseriesNodeConfiguration, TimeseriesNodeConfiguration,
TimeseriesNodeConfigurationForm TimeseriesNodeConfigurationForm
} from '@home/components/rule-node/action/timeseries-config.models'; } from '@home/components/rule-node/action/timeseries-config.models';
@ -37,9 +37,9 @@ export class TimeseriesConfigComponent extends RuleNodeConfigurationComponent {
timeseriesConfigForm: FormGroup; timeseriesConfigForm: FormGroup;
PersistenceType = PersistenceType; PersistenceType = ProcessingType;
persistenceStrategies = [PersistenceType.ON_EVERY_MESSAGE, PersistenceType.DEDUPLICATE, PersistenceType.WEBSOCKETS_ONLY]; persistenceStrategies = [ProcessingType.ON_EVERY_MESSAGE, ProcessingType.DEDUPLICATE, ProcessingType.WEBSOCKETS_ONLY];
PersistenceTypeTranslationMap = PersistenceTypeTranslationMap; PersistenceTypeTranslationMap = ProcessingTypeTranslationMap;
maxDeduplicateTime = maxDeduplicateTimeSecs maxDeduplicateTime = maxDeduplicateTimeSecs
@ -52,22 +52,22 @@ export class TimeseriesConfigComponent extends RuleNodeConfigurationComponent {
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {
return ['persistenceSettings.isAdvanced', 'persistenceSettings.type']; return ['processingSettings.isAdvanced', 'processingSettings.type'];
} }
protected prepareInputConfig(config: TimeseriesNodeConfiguration): TimeseriesNodeConfigurationForm { protected prepareInputConfig(config: TimeseriesNodeConfiguration): TimeseriesNodeConfigurationForm {
let persistenceSettings: PersistenceSettingsForm; let processingSettings: ProcessingSettingsForm;
if (config?.persistenceSettings) { if (config?.processingSettings) {
const isAdvanced = config?.persistenceSettings?.type === PersistenceType.ADVANCED; const isAdvanced = config?.processingSettings?.type === ProcessingType.ADVANCED;
persistenceSettings = { processingSettings = {
type: isAdvanced ? PersistenceType.ON_EVERY_MESSAGE : config.persistenceSettings.type, type: isAdvanced ? ProcessingType.ON_EVERY_MESSAGE : config.processingSettings.type,
isAdvanced: isAdvanced, isAdvanced: isAdvanced,
deduplicationIntervalSecs: config.persistenceSettings?.deduplicationIntervalSecs ?? 60, deduplicationIntervalSecs: config.processingSettings?.deduplicationIntervalSecs ?? 60,
advanced: isAdvanced ? config.persistenceSettings : defaultAdvancedPersistenceStrategy advanced: isAdvanced ? config.processingSettings : defaultAdvancedPersistenceStrategy
} }
} else { } else {
persistenceSettings = { processingSettings = {
type: PersistenceType.ON_EVERY_MESSAGE, type: ProcessingType.ON_EVERY_MESSAGE,
isAdvanced: false, isAdvanced: false,
deduplicationIntervalSecs: 60, deduplicationIntervalSecs: 60,
advanced: defaultAdvancedPersistenceStrategy advanced: defaultAdvancedPersistenceStrategy
@ -75,36 +75,36 @@ export class TimeseriesConfigComponent extends RuleNodeConfigurationComponent {
} }
return { return {
...config, ...config,
persistenceSettings: persistenceSettings processingSettings: processingSettings
} }
} }
protected prepareOutputConfig(config: TimeseriesNodeConfigurationForm): TimeseriesNodeConfiguration { protected prepareOutputConfig(config: TimeseriesNodeConfigurationForm): TimeseriesNodeConfiguration {
let persistenceSettings: PersistenceSettings; let processingSettings: ProcessingSettings;
if (config.persistenceSettings.isAdvanced) { if (config.processingSettings.isAdvanced) {
persistenceSettings = { processingSettings = {
...config.persistenceSettings.advanced, ...config.processingSettings.advanced,
type: PersistenceType.ADVANCED type: ProcessingType.ADVANCED
}; };
} else { } else {
persistenceSettings = { processingSettings = {
type: config.persistenceSettings.type, type: config.processingSettings.type,
deduplicationIntervalSecs: config.persistenceSettings?.deduplicationIntervalSecs deduplicationIntervalSecs: config.processingSettings?.deduplicationIntervalSecs
}; };
} }
return { return {
...config, ...config,
persistenceSettings processingSettings
}; };
} }
protected onConfigurationSet(config: TimeseriesNodeConfigurationForm) { protected onConfigurationSet(config: TimeseriesNodeConfigurationForm) {
this.timeseriesConfigForm = this.fb.group({ this.timeseriesConfigForm = this.fb.group({
persistenceSettings: this.fb.group({ processingSettings: this.fb.group({
isAdvanced: [config?.persistenceSettings?.isAdvanced ?? false], isAdvanced: [config?.processingSettings?.isAdvanced ?? false],
type: [config?.persistenceSettings?.type ?? PersistenceType.ON_EVERY_MESSAGE], type: [config?.processingSettings?.type ?? ProcessingType.ON_EVERY_MESSAGE],
deduplicationIntervalSecs: [ deduplicationIntervalSecs: [
{value: config?.persistenceSettings?.deduplicationIntervalSecs ?? 60, disabled: true}, {value: config?.processingSettings?.deduplicationIntervalSecs ?? 60, disabled: true},
[Validators.required, Validators.max(maxDeduplicateTimeSecs)] [Validators.required, Validators.max(maxDeduplicateTimeSecs)]
], ],
advanced: [{value: null, disabled: true}] advanced: [{value: null, disabled: true}]
@ -115,18 +115,18 @@ export class TimeseriesConfigComponent extends RuleNodeConfigurationComponent {
} }
protected updateValidators(emitEvent: boolean, _trigger?: string) { protected updateValidators(emitEvent: boolean, _trigger?: string) {
const persistenceForm = this.timeseriesConfigForm.get('persistenceSettings') as FormGroup; const processingForm = this.timeseriesConfigForm.get('processingSettings') as FormGroup;
const isAdvanced: boolean = persistenceForm.get('isAdvanced').value; const isAdvanced: boolean = processingForm.get('isAdvanced').value;
const type: PersistenceType = persistenceForm.get('type').value; const type: ProcessingType = processingForm.get('type').value;
if (!isAdvanced && type === PersistenceType.DEDUPLICATE) { if (!isAdvanced && type === ProcessingType.DEDUPLICATE) {
persistenceForm.get('deduplicationIntervalSecs').enable({emitEvent}); processingForm.get('deduplicationIntervalSecs').enable({emitEvent});
} else { } else {
persistenceForm.get('deduplicationIntervalSecs').disable({emitEvent}); processingForm.get('deduplicationIntervalSecs').disable({emitEvent});
} }
if (isAdvanced) { if (isAdvanced) {
persistenceForm.get('advanced').enable({emitEvent}); processingForm.get('advanced').enable({emitEvent});
} else { } else {
persistenceForm.get('advanced').disable({emitEvent}); processingForm.get('advanced').disable({emitEvent});
} }
} }
} }

View File

@ -19,24 +19,24 @@ import { DAY, SECOND } from '@shared/models/time/time.models';
export const maxDeduplicateTimeSecs = DAY / SECOND; export const maxDeduplicateTimeSecs = DAY / SECOND;
export interface TimeseriesNodeConfiguration { export interface TimeseriesNodeConfiguration {
persistenceSettings: PersistenceSettings; processingSettings: ProcessingSettings;
defaultTTL: number; defaultTTL: number;
useServerTs: boolean; useServerTs: boolean;
} }
export interface TimeseriesNodeConfigurationForm extends Omit<TimeseriesNodeConfiguration, 'persistenceSettings'> { export interface TimeseriesNodeConfigurationForm extends Omit<TimeseriesNodeConfiguration, 'processingSettings'> {
persistenceSettings: PersistenceSettingsForm processingSettings: ProcessingSettingsForm
} }
export type PersistenceSettings = BasicPersistenceSettings & Partial<DeduplicatePersistenceStrategy> & Partial<AdvancedPersistenceStrategy>; export type ProcessingSettings = ProcessingSettingsSettings & Partial<DeduplicateProcessingStrategy> & Partial<AdvancedProcessingStrategy>;
export type PersistenceSettingsForm = Omit<PersistenceSettings, keyof AdvancedPersistenceStrategy> & { export type ProcessingSettingsForm = Omit<ProcessingSettings, keyof AdvancedProcessingStrategy> & {
isAdvanced: boolean; isAdvanced: boolean;
advanced?: Partial<AdvancedPersistenceStrategy>; advanced?: Partial<AdvancedProcessingStrategy>;
type: PersistenceType; type: ProcessingType;
}; };
export enum PersistenceType { export enum ProcessingType {
ON_EVERY_MESSAGE = 'ON_EVERY_MESSAGE', ON_EVERY_MESSAGE = 'ON_EVERY_MESSAGE',
DEDUPLICATE = 'DEDUPLICATE', DEDUPLICATE = 'DEDUPLICATE',
WEBSOCKETS_ONLY = 'WEBSOCKETS_ONLY', WEBSOCKETS_ONLY = 'WEBSOCKETS_ONLY',
@ -44,35 +44,35 @@ export enum PersistenceType {
SKIP = 'SKIP' SKIP = 'SKIP'
} }
export const PersistenceTypeTranslationMap = new Map<PersistenceType, string>([ export const ProcessingTypeTranslationMap = new Map<ProcessingType, string>([
[PersistenceType.ON_EVERY_MESSAGE, 'rule-node-config.save-time-series.strategy-type.every-message'], [ProcessingType.ON_EVERY_MESSAGE, 'rule-node-config.save-time-series.strategy-type.every-message'],
[PersistenceType.DEDUPLICATE, 'rule-node-config.save-time-series.strategy-type.deduplicate'], [ProcessingType.DEDUPLICATE, 'rule-node-config.save-time-series.strategy-type.deduplicate'],
[PersistenceType.WEBSOCKETS_ONLY, 'rule-node-config.save-time-series.strategy-type.web-sockets-only'], [ProcessingType.WEBSOCKETS_ONLY, 'rule-node-config.save-time-series.strategy-type.web-sockets-only'],
[PersistenceType.SKIP, 'rule-node-config.save-time-series.strategy-type.skip'], [ProcessingType.SKIP, 'rule-node-config.save-time-series.strategy-type.skip'],
]) ])
export interface BasicPersistenceSettings { export interface ProcessingSettingsSettings {
type: PersistenceType; type: ProcessingType;
} }
export interface DeduplicatePersistenceStrategy extends BasicPersistenceSettings{ export interface DeduplicateProcessingStrategy extends ProcessingSettingsSettings{
deduplicationIntervalSecs: number; deduplicationIntervalSecs: number;
} }
export interface AdvancedPersistenceStrategy extends BasicPersistenceSettings{ export interface AdvancedProcessingStrategy extends ProcessingSettingsSettings{
timeseries: AdvancedPersistenceConfig; timeseries: AdvancedProcessingConfig;
latest: AdvancedPersistenceConfig; latest: AdvancedProcessingConfig;
webSockets: AdvancedPersistenceConfig; webSockets: AdvancedProcessingConfig;
} }
export type AdvancedPersistenceConfig = WithOptional<DeduplicatePersistenceStrategy, 'deduplicationIntervalSecs'>; export type AdvancedProcessingConfig = WithOptional<DeduplicateProcessingStrategy, 'deduplicationIntervalSecs'>;
export const defaultAdvancedPersistenceConfig: AdvancedPersistenceConfig = { export const defaultAdvancedProcessingConfig: AdvancedProcessingConfig = {
type: PersistenceType.ON_EVERY_MESSAGE type: ProcessingType.ON_EVERY_MESSAGE
} }
export const defaultAdvancedPersistenceStrategy: Omit<AdvancedPersistenceStrategy, 'type'> = { export const defaultAdvancedPersistenceStrategy: Omit<AdvancedProcessingStrategy, 'type'> = {
timeseries: defaultAdvancedPersistenceConfig, timeseries: defaultAdvancedProcessingConfig,
latest: defaultAdvancedPersistenceConfig, latest: defaultAdvancedProcessingConfig,
webSockets: defaultAdvancedPersistenceConfig, webSockets: defaultAdvancedProcessingConfig,
} }

View File

@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<div [hidden]="!hintText" class="tb-form-hint tb-primary-fill space-between"> <div [hidden]="!hintText" class="tb-form-hint tb-primary-fill flex justify-between gap-5">
<div [innerHTML]=" hintText | translate | safe: 'html'" <div [innerHTML]=" hintText | translate | safe: 'html'"
[style.text-align]="textAlign" [style.text-align]="textAlign"
class="hint-text"></div> class="w-full"></div>
<div *ngIf="popupHelpLink" class="see-example" tb-help-popup="{{ popupHelpLink }}" <div *ngIf="popupHelpLink" class="flex shrink-0" tb-help-popup="{{ popupHelpLink }}"
hintMode hintMode
tb-help-popup-placement="right" tb-help-popup-placement="right"
trigger-style="letter-spacing:0.25px; font-size:12px" trigger-style="letter-spacing:0.25px; font-size:12px"

View File

@ -1,31 +0,0 @@
/**
* Copyright © 2016-2024 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.
*/
:host {
.space-between {
display: flex;
justify-content: space-between;
gap: 20px;
.see-example {
display: flex;
flex-shrink: 0;
}
}
.hint-text {
width: 100%;
}
}

View File

@ -19,7 +19,7 @@ import { Component, Input } from '@angular/core';
@Component({ @Component({
selector: 'tb-example-hint', selector: 'tb-example-hint',
templateUrl: './example-hint.component.html', templateUrl: './example-hint.component.html',
styleUrls: ['./example-hint.component.scss'] styleUrls: []
}) })
export class ExampleHintComponent { export class ExampleHintComponent {
@Input() hintText: string; @Input() hintText: string;

View File

@ -0,0 +1,3 @@
**TO DO;**
Advanced mode doc

View File

@ -5134,8 +5134,9 @@
"basic-mode": "Basic", "basic-mode": "Basic",
"advanced-mode": "Advanced", "advanced-mode": "Advanced",
"save-time-series": { "save-time-series": {
"persistence-settings": "Persistence settings", "processing-settings": "Processing settings",
"persistence-settings-hint": "Define how and when time series data is saved. In Basic mode, apply a single persistence strategy to all actions or enable only WebSockets updates. Advanced mode allows you to configure individual persistence strategies for each action.", "processing-settings-hint": "Define how time series data is processed. In Basic mode, select a preconfigured processing strategy or enable only WebSocket updates. Advanced mode allows you to select individual processing strategies for each action.",
"advanced-settings-hint": "Advanced settings mode is dangerous, and if you do not understand it, you may lose telemetry.\n\n\n\n\n\n\n\n",
"strategy": "Strategy", "strategy": "Strategy",
"deduplication-interval": "Deduplication interval", "deduplication-interval": "Deduplication interval",
"deduplication-interval-required": "Deduplication interval is required", "deduplication-interval-required": "Deduplication interval is required",