UI: Add persistence settings in save ts rule node
This commit is contained in:
		
							parent
							
								
									b898ca4d15
								
							
						
					
					
						commit
						2d1ead5f54
					
				@ -42,6 +42,12 @@ import { DeleteAttributesConfigComponent } from './delete-attributes-config.comp
 | 
			
		||||
import { MathFunctionConfigComponent } from './math-function-config.component';
 | 
			
		||||
import { DeviceStateConfigComponent } from './device-state-config.component';
 | 
			
		||||
import { SendRestApiCallReplyConfigComponent } from './send-rest-api-call-reply-config.component';
 | 
			
		||||
import {
 | 
			
		||||
  AdvancedPersistenceSettingComponent
 | 
			
		||||
} from '@home/components/rule-node/action/advanced-persistence-setting.component';
 | 
			
		||||
import {
 | 
			
		||||
  AdvancedPersistenceSettingRowComponent
 | 
			
		||||
} from '@home/components/rule-node/action/advanced-persistence-setting-row.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@ -67,7 +73,9 @@ import { SendRestApiCallReplyConfigComponent } from './send-rest-api-call-reply-
 | 
			
		||||
    PushToEdgeConfigComponent,
 | 
			
		||||
    PushToCloudConfigComponent,
 | 
			
		||||
    MathFunctionConfigComponent,
 | 
			
		||||
    DeviceStateConfigComponent
 | 
			
		||||
    DeviceStateConfigComponent,
 | 
			
		||||
    AdvancedPersistenceSettingComponent,
 | 
			
		||||
    AdvancedPersistenceSettingRowComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,39 @@
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<section [formGroup]="persistenceSettingRowForm" class="tb-form-panel stroked no-gap no-padding-bottom">
 | 
			
		||||
  <div class="tb-form-panel-title mb-4">{{ title }}</div>
 | 
			
		||||
  <mat-form-field>
 | 
			
		||||
    <mat-label translate>rule-node-config.save-time-series.strategy</mat-label>
 | 
			
		||||
    <mat-select formControlName="type">
 | 
			
		||||
      @for (strategy of persistenceStrategies; track strategy) {
 | 
			
		||||
        <mat-option [value]="strategy">{{ PersistenceTypeTranslationMap.get(strategy) | translate }}</mat-option>
 | 
			
		||||
      }
 | 
			
		||||
    </mat-select>
 | 
			
		||||
  </mat-form-field>
 | 
			
		||||
  @if(persistenceSettingRowForm.get('type').value === PersistenceType.DEDUPLICATE) {
 | 
			
		||||
    <tb-time-unit-input
 | 
			
		||||
      required
 | 
			
		||||
      labelText="{{ 'rule-node-config.save-time-series.deduplication-interval' | translate }}"
 | 
			
		||||
      requiredText="{{ 'rule-node-config.save-time-series.deduplication-interval-required' | translate }}"
 | 
			
		||||
      minErrorText="{{ 'rule-node-config.save-time-series.deduplication-interval-min-max-range' | translate }}"
 | 
			
		||||
      maxErrorText="{{ 'rule-node-config.save-time-series.deduplication-interval-min-max-range' | translate }}"
 | 
			
		||||
      [maxTime]="maxDeduplicateTime"
 | 
			
		||||
      formControlName="deduplicationIntervalSecs">
 | 
			
		||||
    </tb-time-unit-input>
 | 
			
		||||
  }
 | 
			
		||||
</section>
 | 
			
		||||
@ -0,0 +1,114 @@
 | 
			
		||||
///
 | 
			
		||||
/// 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.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
import { Component, forwardRef, Input } from '@angular/core';
 | 
			
		||||
import {
 | 
			
		||||
  ControlValueAccessor,
 | 
			
		||||
  FormBuilder,
 | 
			
		||||
  NG_VALIDATORS,
 | 
			
		||||
  NG_VALUE_ACCESSOR,
 | 
			
		||||
  ValidationErrors,
 | 
			
		||||
  Validator
 | 
			
		||||
} from '@angular/forms';
 | 
			
		||||
import {
 | 
			
		||||
  AdvancedPersistenceConfig,
 | 
			
		||||
  defaultAdvancedPersistenceConfig,
 | 
			
		||||
  maxDeduplicateTimeSecs,
 | 
			
		||||
  PersistenceType,
 | 
			
		||||
  PersistenceTypeTranslationMap
 | 
			
		||||
} from '@home/components/rule-node/action/timeseries-config.models';
 | 
			
		||||
import { isDefinedAndNotNull } from '@core/utils';
 | 
			
		||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-advanced-persistence-setting-row',
 | 
			
		||||
  templateUrl: './advanced-persistence-setting-row.component.html',
 | 
			
		||||
  providers: [{
 | 
			
		||||
    provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
    useExisting: forwardRef(() => AdvancedPersistenceSettingRowComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  },{
 | 
			
		||||
    provide: NG_VALIDATORS,
 | 
			
		||||
    useExisting: forwardRef(() => AdvancedPersistenceSettingRowComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  }]
 | 
			
		||||
})
 | 
			
		||||
export class AdvancedPersistenceSettingRowComponent implements ControlValueAccessor, Validator {
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  title: string;
 | 
			
		||||
 | 
			
		||||
  persistenceSettingRowForm = this.fb.group({
 | 
			
		||||
    type: [defaultAdvancedPersistenceConfig.type],
 | 
			
		||||
    deduplicationIntervalSecs: [{value: 60, disabled: true}]
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  PersistenceType = PersistenceType;
 | 
			
		||||
  persistenceStrategies = [PersistenceType.ON_EVERY_MESSAGE, PersistenceType.DEDUPLICATE, PersistenceType.SKIP];
 | 
			
		||||
  PersistenceTypeTranslationMap = PersistenceTypeTranslationMap;
 | 
			
		||||
 | 
			
		||||
  maxDeduplicateTime = maxDeduplicateTimeSecs;
 | 
			
		||||
 | 
			
		||||
  private propagateChange: (value: any) => void = () => {};
 | 
			
		||||
 | 
			
		||||
  constructor(private fb: FormBuilder) {
 | 
			
		||||
    this.persistenceSettingRowForm.get('type').valueChanges.pipe(
 | 
			
		||||
      takeUntilDestroyed()
 | 
			
		||||
    ).subscribe(() => this.updatedValidation());
 | 
			
		||||
 | 
			
		||||
    this.persistenceSettingRowForm.valueChanges.pipe(
 | 
			
		||||
      takeUntilDestroyed()
 | 
			
		||||
    ).subscribe((value) => this.propagateChange(value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  registerOnChange(fn: any) {
 | 
			
		||||
    this.propagateChange = fn;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  registerOnTouched(_fn: any) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDisabledState(isDisabled: boolean) {
 | 
			
		||||
    if (isDisabled) {
 | 
			
		||||
      this.persistenceSettingRowForm.disable({emitEvent: false});
 | 
			
		||||
    } else {
 | 
			
		||||
      this.persistenceSettingRowForm.enable({emitEvent: false});
 | 
			
		||||
      this.updatedValidation();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  validate(): ValidationErrors | null {
 | 
			
		||||
    return this.persistenceSettingRowForm.valid ? null : {
 | 
			
		||||
      persistenceSettingRow: false
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  writeValue(value: AdvancedPersistenceConfig) {
 | 
			
		||||
    if (isDefinedAndNotNull(value)) {
 | 
			
		||||
      this.persistenceSettingRowForm.patchValue(value, {emitEvent: false});
 | 
			
		||||
    } else {
 | 
			
		||||
      this.persistenceSettingRowForm.patchValue(defaultAdvancedPersistenceConfig);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private updatedValidation() {
 | 
			
		||||
    if (this.persistenceSettingRowForm.get('type').value === PersistenceType.DEDUPLICATE) {
 | 
			
		||||
      this.persistenceSettingRowForm.get('deduplicationIntervalSecs').enable({emitEvent: false});
 | 
			
		||||
    } else {
 | 
			
		||||
      this.persistenceSettingRowForm.get('deduplicationIntervalSecs').disable({emitEvent: false})
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,31 @@
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<section [formGroup]="persistenceForm" class="tb-form-panel no-border no-padding">
 | 
			
		||||
  <tb-advanced-persistence-setting-row
 | 
			
		||||
    formControlName="timeseries"
 | 
			
		||||
    title="{{ 'rule-node-config.save-time-series.time-series' | translate }}"
 | 
			
		||||
  ></tb-advanced-persistence-setting-row>
 | 
			
		||||
  <tb-advanced-persistence-setting-row
 | 
			
		||||
    formControlName="latest"
 | 
			
		||||
    title="{{ 'rule-node-config.save-time-series.latest' | translate }}"
 | 
			
		||||
  ></tb-advanced-persistence-setting-row>
 | 
			
		||||
  <tb-advanced-persistence-setting-row
 | 
			
		||||
    formControlName="webSockets"
 | 
			
		||||
    title="{{ 'rule-node-config.save-time-series.web-sockets' | translate }}"
 | 
			
		||||
  ></tb-advanced-persistence-setting-row>
 | 
			
		||||
</section>
 | 
			
		||||
@ -0,0 +1,83 @@
 | 
			
		||||
///
 | 
			
		||||
/// 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.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  ControlValueAccessor,
 | 
			
		||||
  FormBuilder,
 | 
			
		||||
  NG_VALIDATORS,
 | 
			
		||||
  NG_VALUE_ACCESSOR,
 | 
			
		||||
  ValidationErrors,
 | 
			
		||||
  Validator
 | 
			
		||||
} from '@angular/forms';
 | 
			
		||||
import { Component, forwardRef } from '@angular/core';
 | 
			
		||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 | 
			
		||||
import { AdvancedPersistenceStrategy } from '@home/components/rule-node/action/timeseries-config.models';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-advanced-persistence-settings',
 | 
			
		||||
  templateUrl: './advanced-persistence-setting.component.html',
 | 
			
		||||
  providers: [{
 | 
			
		||||
    provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
    useExisting: forwardRef(() => AdvancedPersistenceSettingComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  },{
 | 
			
		||||
    provide: NG_VALIDATORS,
 | 
			
		||||
    useExisting: forwardRef(() => AdvancedPersistenceSettingComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  }]
 | 
			
		||||
})
 | 
			
		||||
export class AdvancedPersistenceSettingComponent implements ControlValueAccessor, Validator {
 | 
			
		||||
 | 
			
		||||
  persistenceForm = this.fb.group({
 | 
			
		||||
    timeseries: [null],
 | 
			
		||||
    latest: [null],
 | 
			
		||||
    webSockets: [null]
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  private propagateChange: (value: any) => void = () => {};
 | 
			
		||||
 | 
			
		||||
  constructor(private fb: FormBuilder) {
 | 
			
		||||
    this.persistenceForm.valueChanges.pipe(
 | 
			
		||||
      takeUntilDestroyed()
 | 
			
		||||
    ).subscribe(value => this.propagateChange(value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  registerOnChange(fn: any) {
 | 
			
		||||
    this.propagateChange = fn;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  registerOnTouched(_fn: any) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDisabledState(isDisabled: boolean) {
 | 
			
		||||
    if (isDisabled) {
 | 
			
		||||
      this.persistenceForm.disable({emitEvent: false});
 | 
			
		||||
    } else {
 | 
			
		||||
      this.persistenceForm.enable({emitEvent: false});
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  validate(): ValidationErrors | null {
 | 
			
		||||
    return this.persistenceForm.valid ? null : {
 | 
			
		||||
      persistenceForm: false
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  writeValue(value: AdvancedPersistenceStrategy) {
 | 
			
		||||
    this.persistenceForm.patchValue(value, {emitEvent: false});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -16,6 +16,46 @@
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<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="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>
 | 
			
		||||
        rule-node-config.save-time-series.persistence-settings
 | 
			
		||||
      </div>
 | 
			
		||||
      <tb-toggle-select appearance="fill" selectMediaBreakpoint="xs"
 | 
			
		||||
                        formControlName="isAdvanced">
 | 
			
		||||
        <tb-toggle-option [value]=false>{{ 'rule-node-config.basic-mode' | translate}}</tb-toggle-option>
 | 
			
		||||
        <tb-toggle-option [value]=true>{{ 'rule-node-config.advanced-mode' | translate }}</tb-toggle-option>
 | 
			
		||||
      </tb-toggle-select>
 | 
			
		||||
    </div>
 | 
			
		||||
    @if(!timeseriesConfigForm.get('persistenceSettings.isAdvanced').value) {
 | 
			
		||||
      <mat-form-field>
 | 
			
		||||
        <mat-label translate>rule-node-config.save-time-series.strategy</mat-label>
 | 
			
		||||
        <mat-select formControlName="type">
 | 
			
		||||
          @for (strategy of persistenceStrategies; track strategy) {
 | 
			
		||||
            <mat-option [value]="strategy">{{ PersistenceTypeTranslationMap.get(strategy) | translate }}</mat-option>
 | 
			
		||||
          }
 | 
			
		||||
        </mat-select>
 | 
			
		||||
      </mat-form-field>
 | 
			
		||||
 | 
			
		||||
      @if(timeseriesConfigForm.get('persistenceSettings.type').value === PersistenceType.DEDUPLICATE) {
 | 
			
		||||
        <tb-time-unit-input
 | 
			
		||||
          required
 | 
			
		||||
          labelText="{{ 'rule-node-config.save-time-series.deduplication-interval' | translate }}"
 | 
			
		||||
          requiredText="{{ 'rule-node-config.save-time-series.deduplication-interval-required' | translate }}"
 | 
			
		||||
          minErrorText="{{ 'rule-node-config.save-time-series.deduplication-interval-min-max-range' | translate }}"
 | 
			
		||||
          maxErrorText="{{ 'rule-node-config.save-time-series.deduplication-interval-min-max-range' | translate }}"
 | 
			
		||||
          [maxTime]="maxDeduplicateTime"
 | 
			
		||||
          formControlName="deduplicationIntervalSecs">
 | 
			
		||||
        </tb-time-unit-input>
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    @else {
 | 
			
		||||
      <tb-advanced-persistence-settings
 | 
			
		||||
        class="mb-4"
 | 
			
		||||
        formControlName="advanced"
 | 
			
		||||
      ></tb-advanced-persistence-settings>
 | 
			
		||||
    }
 | 
			
		||||
  </div>
 | 
			
		||||
  <section class="tb-form-panel stroked">
 | 
			
		||||
    <mat-expansion-panel class="tb-settings">
 | 
			
		||||
      <mat-expansion-panel-header>
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
  defaultAdvancedPersistenceStrategy,
 | 
			
		||||
  maxDeduplicateTimeSecs,
 | 
			
		||||
  PersistenceSettings,
 | 
			
		||||
  PersistenceSettingsForm,
 | 
			
		||||
  PersistenceType,
 | 
			
		||||
  PersistenceTypeTranslationMap,
 | 
			
		||||
  TimeseriesNodeConfiguration,
 | 
			
		||||
  TimeseriesNodeConfigurationForm
 | 
			
		||||
} from '@home/components/rule-node/action/timeseries-config.models';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-action-node-timeseries-config',
 | 
			
		||||
@ -25,20 +35,98 @@ import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/m
 | 
			
		||||
})
 | 
			
		||||
export class TimeseriesConfigComponent extends RuleNodeConfigurationComponent {
 | 
			
		||||
 | 
			
		||||
  timeseriesConfigForm: UntypedFormGroup;
 | 
			
		||||
  timeseriesConfigForm: FormGroup;
 | 
			
		||||
 | 
			
		||||
  constructor(private fb: UntypedFormBuilder) {
 | 
			
		||||
  PersistenceType = PersistenceType;
 | 
			
		||||
  persistenceStrategies = [PersistenceType.ON_EVERY_MESSAGE, PersistenceType.DEDUPLICATE, PersistenceType.WEBSOCKETS_ONLY];
 | 
			
		||||
  PersistenceTypeTranslationMap = PersistenceTypeTranslationMap;
 | 
			
		||||
 | 
			
		||||
  maxDeduplicateTime = maxDeduplicateTimeSecs
 | 
			
		||||
 | 
			
		||||
  constructor(private fb: FormBuilder) {
 | 
			
		||||
    super();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected configForm(): UntypedFormGroup {
 | 
			
		||||
  protected configForm(): FormGroup {
 | 
			
		||||
    return this.timeseriesConfigForm;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected onConfigurationSet(configuration: RuleNodeConfiguration) {
 | 
			
		||||
  protected validatorTriggers(): string[] {
 | 
			
		||||
    return ['persistenceSettings.isAdvanced', 'persistenceSettings.type'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected prepareInputConfig(config: TimeseriesNodeConfiguration): TimeseriesNodeConfigurationForm {
 | 
			
		||||
    let persistenceSettings: PersistenceSettingsForm;
 | 
			
		||||
    if (config?.persistenceSettings) {
 | 
			
		||||
      const isAdvanced = config?.persistenceSettings?.type === PersistenceType.ADVANCED;
 | 
			
		||||
      persistenceSettings = {
 | 
			
		||||
        ...config.persistenceSettings,
 | 
			
		||||
        isAdvanced: isAdvanced,
 | 
			
		||||
        type: isAdvanced ? PersistenceType.ON_EVERY_MESSAGE : config.persistenceSettings.type,
 | 
			
		||||
        advanced: isAdvanced ? config.persistenceSettings : defaultAdvancedPersistenceStrategy
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      persistenceSettings = {
 | 
			
		||||
        type: PersistenceType.ON_EVERY_MESSAGE,
 | 
			
		||||
        isAdvanced: false,
 | 
			
		||||
        deduplicationIntervalSecs: 10,
 | 
			
		||||
        advanced: defaultAdvancedPersistenceStrategy
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
      ...config,
 | 
			
		||||
      persistenceSettings: persistenceSettings
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected prepareOutputConfig(config: TimeseriesNodeConfigurationForm): TimeseriesNodeConfiguration {
 | 
			
		||||
    let persistenceSettings: PersistenceSettings;
 | 
			
		||||
    if (config.persistenceSettings.isAdvanced) {
 | 
			
		||||
      persistenceSettings = {
 | 
			
		||||
        ...config.persistenceSettings.advanced,
 | 
			
		||||
        type: PersistenceType.ADVANCED
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      persistenceSettings = {
 | 
			
		||||
        type: config.persistenceSettings.type,
 | 
			
		||||
        deduplicationIntervalSecs: config.persistenceSettings?.deduplicationIntervalSecs
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
      ...config,
 | 
			
		||||
      persistenceSettings
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected onConfigurationSet(config: TimeseriesNodeConfigurationForm) {
 | 
			
		||||
    this.timeseriesConfigForm = this.fb.group({
 | 
			
		||||
      defaultTTL: [configuration ? configuration.defaultTTL : null, [Validators.required, Validators.min(0)]],
 | 
			
		||||
      useServerTs: [configuration ? configuration.useServerTs : false, []]
 | 
			
		||||
      persistenceSettings: this.fb.group({
 | 
			
		||||
        isAdvanced: [config?.persistenceSettings?.isAdvanced ?? false],
 | 
			
		||||
        type: [config?.persistenceSettings?.type ?? PersistenceType.ON_EVERY_MESSAGE],
 | 
			
		||||
        deduplicationIntervalSecs: [
 | 
			
		||||
          {value: config?.persistenceSettings?.deduplicationIntervalSecs ?? 10, 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 persistenceForm = this.timeseriesConfigForm.get('persistenceSettings') as FormGroup;
 | 
			
		||||
    const isAdvanced: boolean = persistenceForm.get('isAdvanced').value;
 | 
			
		||||
    const type: PersistenceType = persistenceForm.get('type').value;
 | 
			
		||||
    if (!isAdvanced && type === PersistenceType.DEDUPLICATE) {
 | 
			
		||||
      persistenceForm.get('deduplicationIntervalSecs').enable({emitEvent});
 | 
			
		||||
    } else {
 | 
			
		||||
      persistenceForm.get('deduplicationIntervalSecs').disable({emitEvent});
 | 
			
		||||
    }
 | 
			
		||||
    if (isAdvanced) {
 | 
			
		||||
      persistenceForm.get('advanced').enable({emitEvent});
 | 
			
		||||
    } else {
 | 
			
		||||
      persistenceForm.get('advanced').disable({emitEvent});
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,78 @@
 | 
			
		||||
///
 | 
			
		||||
/// 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.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
import { DAY, SECOND } from '@shared/models/time/time.models';
 | 
			
		||||
 | 
			
		||||
export const maxDeduplicateTimeSecs = DAY / SECOND + 1;
 | 
			
		||||
 | 
			
		||||
export interface TimeseriesNodeConfiguration {
 | 
			
		||||
  persistenceSettings: PersistenceSettings;
 | 
			
		||||
  defaultTTL: number;
 | 
			
		||||
  useServerTs: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TimeseriesNodeConfigurationForm extends Omit<TimeseriesNodeConfiguration, 'persistenceSettings'> {
 | 
			
		||||
  persistenceSettings: PersistenceSettingsForm
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type PersistenceSettings = BasicPersistenceSettings & Partial<DeduplicatePersistenceStrategy> & Partial<AdvancedPersistenceStrategy>;
 | 
			
		||||
 | 
			
		||||
export type PersistenceSettingsForm = Omit<PersistenceSettings, keyof AdvancedPersistenceStrategy> & {
 | 
			
		||||
  isAdvanced: boolean;
 | 
			
		||||
  advanced?: Partial<AdvancedPersistenceStrategy>;
 | 
			
		||||
  type: PersistenceType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export enum PersistenceType {
 | 
			
		||||
  ON_EVERY_MESSAGE = 'ON_EVERY_MESSAGE',
 | 
			
		||||
  DEDUPLICATE = 'DEDUPLICATE',
 | 
			
		||||
  WEBSOCKETS_ONLY = 'WEBSOCKETS_ONLY',
 | 
			
		||||
  ADVANCED = 'ADVANCED',
 | 
			
		||||
  SKIP = 'SKIP'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const PersistenceTypeTranslationMap = new Map<PersistenceType, string>([
 | 
			
		||||
  [PersistenceType.ON_EVERY_MESSAGE, 'rule-node-config.save-time-series.strategy-type.every-message'],
 | 
			
		||||
  [PersistenceType.DEDUPLICATE, 'rule-node-config.save-time-series.strategy-type.deduplicate'],
 | 
			
		||||
  [PersistenceType.WEBSOCKETS_ONLY, 'rule-node-config.save-time-series.strategy-type.web-sockets-only'],
 | 
			
		||||
  [PersistenceType.SKIP, 'rule-node-config.save-time-series.strategy-type.skip'],
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
export interface BasicPersistenceSettings {
 | 
			
		||||
  type: PersistenceType;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface DeduplicatePersistenceStrategy extends BasicPersistenceSettings{
 | 
			
		||||
  deduplicationIntervalSecs: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface AdvancedPersistenceStrategy extends BasicPersistenceSettings{
 | 
			
		||||
  timeseries: AdvancedPersistenceConfig;
 | 
			
		||||
  latest: AdvancedPersistenceConfig;
 | 
			
		||||
  webSockets: AdvancedPersistenceConfig;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type AdvancedPersistenceConfig = WithOptional<DeduplicatePersistenceStrategy, 'deduplicationIntervalSecs'>;
 | 
			
		||||
 | 
			
		||||
export const defaultAdvancedPersistenceConfig: AdvancedPersistenceConfig = {
 | 
			
		||||
  type: PersistenceType.ON_EVERY_MESSAGE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const defaultAdvancedPersistenceStrategy: Omit<AdvancedPersistenceStrategy, 'type'> = {
 | 
			
		||||
  timeseries: defaultAdvancedPersistenceConfig,
 | 
			
		||||
  latest: defaultAdvancedPersistenceConfig,
 | 
			
		||||
  webSockets: defaultAdvancedPersistenceConfig,
 | 
			
		||||
}
 | 
			
		||||
@ -16,12 +16,13 @@
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<section [formGroup]="timeInputForm" class="flex gap-4">
 | 
			
		||||
  <mat-form-field class="max-w-66% flex-full">
 | 
			
		||||
  <mat-form-field class="max-w-66% flex-full" subscriptSizing="dynamic">
 | 
			
		||||
    <mat-label *ngIf="labelText">{{ labelText }}</mat-label>
 | 
			
		||||
    <input type="number" min="0" step="1" matInput formControlName="time">
 | 
			
		||||
    <div matSuffix>
 | 
			
		||||
      <ng-content select="[matSuffix]"></ng-content>
 | 
			
		||||
    </div>
 | 
			
		||||
    <mat-hint></mat-hint>
 | 
			
		||||
    <mat-error *ngIf="timeInputForm.get('time').hasError('required') && requiredText">
 | 
			
		||||
      {{ requiredText }}
 | 
			
		||||
    </mat-error>
 | 
			
		||||
 | 
			
		||||
@ -5068,6 +5068,25 @@
 | 
			
		||||
        "units": "Units",
 | 
			
		||||
        "tell-failure-aws-lambda": "Tell Failure if AWS Lambda function execution raises exception",
 | 
			
		||||
        "tell-failure-aws-lambda-hint": "Rule node forces failure of message processing if AWS Lambda function execution raises exception.",
 | 
			
		||||
        "basic-mode": "Basic",
 | 
			
		||||
        "advanced-mode": "Advanced",
 | 
			
		||||
        "save-time-series": {
 | 
			
		||||
            "persistence-settings": "Persistence settings",
 | 
			
		||||
            "persistence-settings-hint": "Persistence settings hint",
 | 
			
		||||
            "strategy": "Strategy",
 | 
			
		||||
            "deduplication-interval": "Deduplication interval",
 | 
			
		||||
            "deduplication-interval-required": "Deduplication interval is required",
 | 
			
		||||
            "deduplication-interval-min-max-range": "Deduplication interval should be at least 1 second and at most 1 day",
 | 
			
		||||
            "strategy-type": {
 | 
			
		||||
                "every-message": "On every message",
 | 
			
		||||
                "skip": "Skip",
 | 
			
		||||
                "deduplicate": "Deduplicate",
 | 
			
		||||
                "web-sockets-only": "WebSockets only"
 | 
			
		||||
            },
 | 
			
		||||
            "time-series": "Time series",
 | 
			
		||||
            "latest": "Latest",
 | 
			
		||||
            "web-sockets": "WebSockets"
 | 
			
		||||
        },
 | 
			
		||||
        "key-val": {
 | 
			
		||||
            "key": "Key",
 | 
			
		||||
            "value": "Value",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user