From 9eb37d79afb986c23a958da10001c2331500a186 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 15 Jan 2026 14:12:48 +0200 Subject: [PATCH 01/12] UI: Fixed country autocomplete required --- .../src/app/shared/components/country-autocomplete.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/components/country-autocomplete.component.ts b/ui-ngx/src/app/shared/components/country-autocomplete.component.ts index 4bc49bbbb0..728d8afcbf 100644 --- a/ui-ngx/src/app/shared/components/country-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/country-autocomplete.component.ts @@ -130,7 +130,7 @@ export class CountryAutocompleteComponent implements OnInit, OnChanges, ControlV if (changes.required) { const requiredChanges = changes.required; if (requiredChanges.currentValue !== requiredChanges.previousValue) { - if (requiredChanges.currentValue) { + if (this.required) { this.countryFormGroup.get('country').addValidators(Validators.required); } else { this.countryFormGroup.get('country').removeValidators(Validators.required); From 590d14ccf057832316420fbd98304e3af0a7b013 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 15 Jan 2026 13:29:08 +0200 Subject: [PATCH 02/12] Fixed changed password error key mismatch --- .../pages/security/security.component.html | 2 +- .../home/pages/security/security.component.ts | 34 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/security/security.component.html b/ui-ngx/src/app/modules/home/pages/security/security.component.html index c5d3d25f4f..4fca803187 100644 --- a/ui-ngx/src/app/modules/home/pages/security/security.component.html +++ b/ui-ngx/src/app/modules/home/pages/security/security.component.html @@ -54,7 +54,7 @@ profile.current-password - + {{ 'security.password-requirement.incorrect-password-try-again' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/security/security.component.ts b/ui-ngx/src/app/modules/home/pages/security/security.component.ts index 860dd45a5b..25eb42e673 100644 --- a/ui-ngx/src/app/modules/home/pages/security/security.component.ts +++ b/ui-ngx/src/app/modules/home/pages/security/security.component.ts @@ -333,22 +333,24 @@ export class SecurityComponent extends PageComponent implements OnInit, OnDestro onChangePassword(form: FormGroupDirective): void { if (this.changePassword.valid) { this.authService.changePassword(this.changePassword.get('currentPassword').value, - this.changePassword.get('newPassword').value, {ignoreErrors: true}).subscribe(() => { - this.discardChanges(form); - }, - (error) => { - if (error.status === 400 && error.error.message === 'Current password doesn\'t match!') { - this.changePassword.get('currentPassword').setErrors({differencePassword: true}); - } else if (error.status === 400 && error.error.message.startsWith('Password must')) { - this.loadPasswordPolicy(); - } else if (error.status === 400 && error.error.message.startsWith('Password was already used')) { - this.changePassword.get('newPassword').setErrors({alreadyUsed: error.error.message}); - } else { - this.store.dispatch(new ActionNotificationShow({ - message: error.error.message, - type: 'error', - target: 'changePassword' - })); + this.changePassword.get('newPassword').value, {ignoreErrors: true}).subscribe({ + next: () => { + this.discardChanges(form); + }, + error: (error) => { + if (error.status === 400 && error.error.message === 'Current password doesn\'t match!') { + this.changePassword.get('currentPassword').setErrors({ differencePassword: true }); + } else if (error.status === 400 && error.error.message.startsWith('Password must')) { + this.loadPasswordPolicy(); + } else if (error.status === 400 && error.error.message.startsWith('Password was already used')) { + this.changePassword.get('newPassword').setErrors({ alreadyUsed: error.error.message }); + } else { + this.store.dispatch(new ActionNotificationShow({ + message: error.error.message, + type: 'error', + target: 'changePassword' + })); + } } }); } else { From 9181226c6383892467aa77f50bb988654b5b6a6d Mon Sep 17 00:00:00 2001 From: dshvaika Date: Thu, 15 Jan 2026 15:01:20 +0200 Subject: [PATCH 03/12] Fixed relation action events handling for CFs --- .../calculatedField/CalculatedFieldManagerMessageProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java index 2f4bd0db22..2e4fd4abae 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java @@ -384,7 +384,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware EntityId mainId, MultipleTbCallback parentCallback, TriConsumer relationAction) { - if (isMyPartition(mainId, parentCallback)) { + if (!isMyPartition(mainId, parentCallback)) { return; } List cfsByEntityIdAndProfile = getCalculatedFieldsByEntityIdAndProfile(mainId); From 2ab5e01ddf548e39fabf020a2d938d68a7667046 Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Thu, 15 Jan 2026 15:26:46 +0200 Subject: [PATCH 04/12] UI: Fixed time unit input validators --- .../two-factor-auth-settings.component.ts | 2 + .../components/time-unit-input.component.ts | 68 +++++++++++-------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts index 0593cae081..6995d41879 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts @@ -69,6 +69,8 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI this.build2faSettingsForm(); this.twoFaService.getTwoFaSettings().subscribe((setting) => { this.setAuthConfigFormValue(setting); + this.twoFaFormGroup.markAsUntouched(); + this.twoFaFormGroup.markAsPristine(); }); } diff --git a/ui-ngx/src/app/shared/components/time-unit-input.component.ts b/ui-ngx/src/app/shared/components/time-unit-input.component.ts index ce1c7bd817..f3473bf08d 100644 --- a/ui-ngx/src/app/shared/components/time-unit-input.component.ts +++ b/ui-ngx/src/app/shared/components/time-unit-input.component.ts @@ -134,38 +134,13 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator, if (isDefinedAndNotNull(this.maxTime)) { this.updatedAllowTimeUnitInterval(this.maxTime); } - if (this.required || this.maxTime || isDefinedAndNotNull(this.minTime) || this.stepMultipleOf) { - const timeControl = this.timeInputForm.get('time'); - const validators = [Validators.pattern(/^\d*$/)]; - if (this.required) { - validators.push(Validators.required); - } - if (this.maxTime) { - validators.push((control: AbstractControl) => - Validators.max(Math.floor(this.maxTime / this.timeIntervalsInSec.get(this.timeInputForm.get('timeUnit').value)))(control) - ); - } - if (isDefinedAndNotNull(this.minTime)) { - this.minValueValidator = this.minValue; - validators.push((control: AbstractControl) => - Validators.min(this.minValueValidator)(control) - ); - } - if (isDefinedAndNotNull(this.stepMultipleOf) && this.stepMultipleOf > 0) { - validators.push(this.createStepMultipleOfValidator()); - } - - timeControl.setValidators(validators); - timeControl.updateValueAndValidity({ emitEvent: false }); - } + this.refreshTimeValidators(); this.timeInputForm.get('timeUnit').valueChanges.pipe( takeUntilDestroyed(this.destroyRef) ).subscribe(() => { - if (isDefinedAndNotNull(this.minTime)) { - this.minValueValidator = this.minValue; - } + this.refreshTimeValidators(); this.timeInputForm.get('time').updateValueAndValidity({onlySelf: true}); this.timeInputForm.get('time').markAsTouched({onlySelf: true}); }); @@ -201,11 +176,10 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator, if (isDefinedAndNotNull(this.maxTime)) { this.timeUnits = Object.values(TimeUnit).filter(item => item !== TimeUnit.MILLISECONDS) as TimeUnit[]; this.updatedAllowTimeUnitInterval(this.maxTime); - this.timeInputForm.get('time').updateValueAndValidity({emitEvent: false}); } } - if (propName === 'minTime') { - this.minValueValidator = isDefinedAndNotNull(this.minTime) ? this.minValue : 0; + if (['minTime', 'maxTime', 'required', 'stepMultipleOf'].includes(propName)) { + this.refreshTimeValidators(); } } } @@ -312,4 +286,38 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator, } } + private refreshTimeValidators() { + const timeControl = this.timeInputForm.get('time'); + if (!timeControl) return; + + const currentUnit = this.timeInputForm.get('timeUnit')?.value as TimeUnit; + if (!currentUnit) return; + + const secondsInUnit = this.timeIntervalsInSec.get(currentUnit) ?? 1; + const validators: ValidatorFn[] = [Validators.pattern(/^\d*$/)]; + + if (this.required) { + validators.push(Validators.required); + } + + if (isDefinedAndNotNull(this.minTime)) { + this.minValueValidator = Math.ceil(this.minTime / secondsInUnit); + validators.push(Validators.min(this.minValueValidator)); + } else { + this.minValueValidator = 0; + validators.push(Validators.min(this.minValueValidator)); + } + + if (isDefinedAndNotNull(this.maxTime)) { + const maxInCurrentUnit = Math.floor(this.maxTime / secondsInUnit); + validators.push(Validators.max(maxInCurrentUnit)); + } + + if (isDefinedAndNotNull(this.stepMultipleOf) && this.stepMultipleOf > 0) { + validators.push(this.createStepMultipleOfValidator()); + } + + timeControl.setValidators(validators); + timeControl.updateValueAndValidity({ onlySelf: true, emitEvent: false }); + } } From 4897b336397deff6363401278775a788c8872b85 Mon Sep 17 00:00:00 2001 From: dshvaika Date: Thu, 15 Jan 2026 16:52:56 +0200 Subject: [PATCH 05/12] protection from use latest ts when output type is attributes --- .../ctx/state/BaseCalculatedFieldState.java | 5 +++-- .../cf/ctx/state/CalculatedFieldCtx.java | 6 ++--- .../ctx/state/SimpleCalculatedFieldState.java | 6 ++--- ...titiesAggregationCalculatedFieldState.java | 2 +- .../GeofencingCalculatedFieldState.java | 7 +----- .../PropagationCalculatedFieldState.java | 4 +--- .../BaseCalculatedFieldConfiguration.java | 3 +++ .../configuration/HasUseLatestTsConfig.java | 22 +++++++++++++++++++ ...opagationCalculatedFieldConfiguration.java | 9 +++++++- .../SimpleCalculatedFieldConfiguration.java | 2 +- ...gregationCalculatedFieldConfiguration.java | 3 ++- ...eofencingCalculatedFieldConfiguration.java | 11 +++++++++- 12 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/HasUseLatestTsConfig.java diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java index 9153f31447..8bb78c29dc 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java @@ -21,6 +21,7 @@ import lombok.Getter; import lombok.Setter; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.actors.TbActorRef; +import org.thingsboard.server.common.data.cf.configuration.OutputType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; @@ -142,8 +143,8 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState, protected void validateNewEntry(String key, ArgumentEntry newEntry) { } - protected ObjectNode toSimpleResult(boolean useLatestTs, ObjectNode valuesNode) { - if (!useLatestTs) { + protected ObjectNode toResultNode(ObjectNode valuesNode) { + if (ctx.getOutput().getType() == OutputType.ATTRIBUTES || !ctx.isUseLatestTs()) { return valuesNode; } long latestTs = getLatestTimestamp(); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java index 84a0ab008f..ab38287b89 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.cf.configuration.Argument; import org.thingsboard.server.common.data.cf.configuration.ArgumentType; import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.ExpressionBasedCalculatedFieldConfiguration; +import org.thingsboard.server.common.data.cf.configuration.HasUseLatestTsConfig; import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.cf.configuration.PropagationCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; @@ -187,7 +188,6 @@ public class CalculatedFieldCtx implements Closeable { .collect(Collectors.toList()); if (argBasedConfig instanceof ExpressionBasedCalculatedFieldConfiguration expressionBasedConfig) { this.expression = expressionBasedConfig.getExpression(); - this.useLatestTs = CalculatedFieldType.SIMPLE.equals(calculatedField.getType()) && ((SimpleCalculatedFieldConfiguration) argBasedConfig).isUseLatestTs(); } if (calculatedField.getConfiguration() instanceof GeofencingCalculatedFieldConfiguration geofencingConfig) { geofencingConfig.getZoneGroups().forEach((zoneGroupName, config) -> { @@ -209,8 +209,8 @@ public class CalculatedFieldCtx implements Closeable { if (calculatedField.getConfiguration() instanceof ScheduledUpdateSupportedCalculatedFieldConfiguration scheduledConfig) { this.scheduledUpdateIntervalMillis = scheduledConfig.isScheduledUpdateEnabled() ? TimeUnit.SECONDS.toMillis(scheduledConfig.getScheduledUpdateInterval()) : DISABLED_INTERVAL_VALUE; } - if (calculatedField.getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration aggConfig) { - this.useLatestTs = aggConfig.isUseLatestTs(); + if (calculatedField.getConfiguration() instanceof HasUseLatestTsConfig hasUseLatestTsConfig) { + this.useLatestTs = hasUseLatestTsConfig.isUseLatestTs(); } this.systemContext = systemContext; this.tbelInvokeService = systemContext.getTbelInvokeService(); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java index 0ec0a941d7..b9fbe89bb1 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java @@ -53,7 +53,7 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState { Output output = ctx.getOutput(); Object result = NumberUtils.roundResult(expressionResult, output.getDecimalsByDefault()); - JsonNode outputResult = createResultJson(ctx.isUseLatestTs(), output.getName(), result); + JsonNode outputResult = createResultJson(output.getName(), result); return Futures.immediateFuture(TelemetryCalculatedFieldResult.builder() .outputStrategy(output.getStrategy()) @@ -63,7 +63,7 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState { .build()); } - private JsonNode createResultJson(boolean useLatestTs, String outputName, Object result) { + private JsonNode createResultJson(String outputName, Object result) { ObjectNode valuesNode = JacksonUtil.newObjectNode(); if (result instanceof Double doubleValue) { valuesNode.put(outputName, doubleValue); @@ -72,7 +72,7 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState { } else { valuesNode.set(outputName, JacksonUtil.valueToTree(result)); } - return toSimpleResult(useLatestTs, valuesNode); + return toResultNode(valuesNode); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java index 868e8a15ae..5c5858e3bc 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java @@ -201,7 +201,7 @@ public class RelatedEntitiesAggregationCalculatedFieldState extends BaseCalculat .outputStrategy(output.getStrategy()) .type(output.getType()) .scope(output.getScope()) - .result(toSimpleResult(ctx.isUseLatestTs(), aggResult)) + .result(toResultNode(aggResult)) .build()); } else { return Futures.immediateFuture(TelemetryCalculatedFieldResult.EMPTY); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java index 05cc378209..ea669998bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.cf.ctx.state.geofencing; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -136,7 +135,7 @@ public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState imp .outputStrategy(ctx.getOutput().getStrategy()) .type(outputType) .scope(ctx.getOutput().getScope()) - .result(toResultNode(outputType, valuesNode)) + .result(toResultNode(valuesNode)) .build(); if (relationFutures.isEmpty()) { return Futures.immediateFuture(result); @@ -186,10 +185,6 @@ public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState imp } } - private JsonNode toResultNode(OutputType outputType, ObjectNode valuesNode) { - return toSimpleResult(outputType == OutputType.TIME_SERIES, valuesNode); - } - private GeofencingEvalResult aggregateZoneGroup(List zoneResults) { boolean nowInside = zoneResults.stream().anyMatch(r -> INSIDE.equals(r.status())); boolean prevInside = zoneResults.stream() diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java index d7c43b01d7..7ab0a51af3 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java @@ -24,7 +24,6 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Output; -import org.thingsboard.server.common.data.cf.configuration.OutputType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.util.CollectionsUtil; import org.thingsboard.server.service.cf.CalculatedFieldProcessingService; @@ -171,8 +170,7 @@ public class PropagationCalculatedFieldState extends ScriptCalculatedFieldState throw new IllegalArgumentException("Unsupported argument type: " + argumentEntry.getType() + " detected for argument: " + outputKey + ". " + "Only Latest telemetry or Attribute arguments supported for 'Arguments Only' propagation mode!"); }); - ObjectNode result = toSimpleResult(output.getType() == OutputType.TIME_SERIES, valuesNode); - telemetryCfBuilder.result(result); + telemetryCfBuilder.result(toResultNode(valuesNode)); return telemetryCfBuilder.build(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/BaseCalculatedFieldConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/BaseCalculatedFieldConfiguration.java index e17b50ac98..d2c6ebb78c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/BaseCalculatedFieldConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/BaseCalculatedFieldConfiguration.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.cf.configuration; +import jakarta.validation.constraints.NotNull; import lombok.Data; import java.util.Map; @@ -24,6 +25,8 @@ public abstract class BaseCalculatedFieldConfiguration implements ExpressionBase protected Map arguments; protected String expression; + + @NotNull protected Output output; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/HasUseLatestTsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/HasUseLatestTsConfig.java new file mode 100644 index 0000000000..f8fc449171 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/HasUseLatestTsConfig.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2026 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. + */ +package org.thingsboard.server.common.data.cf.configuration; + +public interface HasUseLatestTsConfig { + + boolean isUseLatestTs(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/PropagationCalculatedFieldConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/PropagationCalculatedFieldConfiguration.java index 4c796b25b0..d0ae8789b9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/PropagationCalculatedFieldConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/PropagationCalculatedFieldConfiguration.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.cf.configuration; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -27,7 +28,7 @@ import java.util.List; @Data @EqualsAndHashCode(callSuper = true) -public class PropagationCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements HasRelationPathLevel { +public class PropagationCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements HasRelationPathLevel, HasUseLatestTsConfig { public static final String PROPAGATION_CONFIG_ARGUMENT = "propagationCtx"; @@ -37,6 +38,12 @@ public class PropagationCalculatedFieldConfiguration extends BaseCalculatedField private boolean applyExpressionToResolvedArguments; + @Override + @JsonIgnore + public boolean isUseLatestTs() { + return output.getType() == OutputType.TIME_SERIES; + } + @Override public CalculatedFieldType getType() { return CalculatedFieldType.PROPAGATION; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/SimpleCalculatedFieldConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/SimpleCalculatedFieldConfiguration.java index 00e998ad28..53210d1cf8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/SimpleCalculatedFieldConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/SimpleCalculatedFieldConfiguration.java @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldType; @Data @EqualsAndHashCode(callSuper = true) -public class SimpleCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements ExpressionBasedCalculatedFieldConfiguration { +public class SimpleCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements ExpressionBasedCalculatedFieldConfiguration, HasUseLatestTsConfig { private boolean useLatestTs; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/RelatedEntitiesAggregationCalculatedFieldConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/RelatedEntitiesAggregationCalculatedFieldConfiguration.java index 3b0df7ed82..d905248aa5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/RelatedEntitiesAggregationCalculatedFieldConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/RelatedEntitiesAggregationCalculatedFieldConfiguration.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Argument; import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.HasRelationPathLevel; +import org.thingsboard.server.common.data.cf.configuration.HasUseLatestTsConfig; import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.cf.configuration.ScheduledUpdateSupportedCalculatedFieldConfiguration; import org.thingsboard.server.common.data.relation.RelationPathLevel; @@ -30,7 +31,7 @@ import org.thingsboard.server.common.data.relation.RelationPathLevel; import java.util.Map; @Data -public class RelatedEntitiesAggregationCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration, ScheduledUpdateSupportedCalculatedFieldConfiguration, HasRelationPathLevel { +public class RelatedEntitiesAggregationCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration, ScheduledUpdateSupportedCalculatedFieldConfiguration, HasRelationPathLevel, HasUseLatestTsConfig { @NotNull private RelationPathLevel relation; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfiguration.java index 2d1abd0024..cb408c5dbf 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfiguration.java @@ -22,7 +22,9 @@ import lombok.Data; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Argument; import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration; +import org.thingsboard.server.common.data.cf.configuration.HasUseLatestTsConfig; import org.thingsboard.server.common.data.cf.configuration.Output; +import org.thingsboard.server.common.data.cf.configuration.OutputType; import org.thingsboard.server.common.data.cf.configuration.ScheduledUpdateSupportedCalculatedFieldConfiguration; import org.thingsboard.server.common.data.id.EntityId; @@ -35,7 +37,7 @@ import java.util.Set; import static java.util.stream.Collectors.toSet; @Data -public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration, ScheduledUpdateSupportedCalculatedFieldConfiguration { +public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration, ScheduledUpdateSupportedCalculatedFieldConfiguration, HasUseLatestTsConfig { @Valid @NotNull @@ -48,8 +50,15 @@ public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCal private boolean scheduledUpdateEnabled; private int scheduledUpdateInterval; + @NotNull private Output output; + @Override + @JsonIgnore + public boolean isUseLatestTs() { + return output.getType() == OutputType.TIME_SERIES; + } + @Override public CalculatedFieldType getType() { return CalculatedFieldType.GEOFENCING; From 7b7e5ba93c265bf04620c0a4ad7108ddd16ec92e Mon Sep 17 00:00:00 2001 From: dshvaika Date: Thu, 15 Jan 2026 18:22:15 +0200 Subject: [PATCH 06/12] protection from use latest ts when output type is set to attributes --- .../ctx/state/SimpleCalculatedFieldState.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java index a6e81051a8..0d018f5def 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java @@ -25,6 +25,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.script.api.tbel.TbUtils; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Output; +import org.thingsboard.server.common.data.cf.configuration.OutputType; import org.thingsboard.server.common.data.kv.BasicKvEntry; import org.thingsboard.server.service.cf.CalculatedFieldResult; @@ -74,7 +75,7 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState { Output output = ctx.getOutput(); Object result = formatResult(expressionResult, output.getDecimalsByDefault()); - JsonNode outputResult = createResultJson(ctx.isUseLatestTs(), output.getName(), result); + JsonNode outputResult = createResultJson(ctx, result); return Futures.immediateFuture(new CalculatedFieldResult(output.getType(), output.getScope(), outputResult)); } @@ -89,7 +90,9 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState { return TbUtils.toFixed(expressionResult, decimals); } - private JsonNode createResultJson(boolean useLatestTs, String outputName, Object result) { + private JsonNode createResultJson(CalculatedFieldCtx ctx, Object result) { + Output output = ctx.getOutput(); + String outputName = output.getName(); ObjectNode valuesNode = JacksonUtil.newObjectNode(); if (result instanceof Double doubleValue) { valuesNode.put(outputName, doubleValue); @@ -98,16 +101,17 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState { } else { valuesNode.set(outputName, JacksonUtil.valueToTree(result)); } - + if (output.getType() == OutputType.ATTRIBUTES || !ctx.isUseLatestTs()) { + return valuesNode; + } long latestTs = getLatestTimestamp(); - if (useLatestTs && latestTs != DEFAULT_LAST_UPDATE_TS) { - ObjectNode resultNode = JacksonUtil.newObjectNode(); - resultNode.put("ts", latestTs); - resultNode.set("values", valuesNode); - return resultNode; - } else { + if (latestTs == DEFAULT_LAST_UPDATE_TS) { return valuesNode; } + ObjectNode resultNode = JacksonUtil.newObjectNode(); + resultNode.put("ts", latestTs); + resultNode.set("values", valuesNode); + return resultNode; } } From 1e2ffcfc32e86afbb49cc1f432858bde4622cd75 Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Thu, 15 Jan 2026 12:14:52 +0200 Subject: [PATCH 07/12] UI: Fixed alarm rule filter list value type --- .../filter/alarm-rule-filter-list.component.html | 2 +- .../filter/alarm-rule-filter-list.component.ts | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-list.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-list.component.html index 20b0056014..950d19abd0 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-list.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-list.component.html @@ -41,7 +41,7 @@
{{ filterControl.value?.argument }}
-
{{ FilterPredicateTypeTranslationMap.get(filterControl.value?.valueType) | translate }}
+
{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[filterControl.value?.valueType]).name | translate }}