From 924e00e26e3fa0c2a43b909b395cbfcb6d363bb1 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 10 Dec 2025 19:14:38 +0200 Subject: [PATCH 01/62] fixed EDQ for sysadmin: tenant and tenant profile entities are allowed --- .../controller/EntityQueryController.java | 2 +- .../controller/EntityQueryControllerTest.java | 64 +++++++++++++++++++ .../server/dao/entity/BaseEntityService.java | 2 +- .../query/DefaultEntityQueryRepository.java | 3 +- 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java index 70fa3a5ed0..fb080e854d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java @@ -82,7 +82,7 @@ public class EntityQueryController extends BaseController { } @ApiOperation(value = "Find Entity Data by Query", notes = ENTITY_DATA_QUERY_DESCRIPTION) - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/entitiesQuery/find", method = RequestMethod.POST) @ResponseBody public PageData findEntityDataByQuery( diff --git a/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java index 93d67ccd4b..8e6d90dfdc 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -26,6 +27,7 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.ResultActions; import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DataConstants; @@ -33,6 +35,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSeverity; @@ -62,6 +65,7 @@ import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.RelationsQueryFilter; +import org.thingsboard.server.common.data.query.SingleEntityFilter; import org.thingsboard.server.common.data.query.StringFilterPredicate; import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.queue.QueueStats; @@ -69,6 +73,8 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.queue.QueueStatsService; import org.thingsboard.server.dao.service.DaoSqlTest; @@ -210,6 +216,64 @@ public class EntityQueryControllerTest extends AbstractControllerTest { countByQueryAndCheck(countQuery, 97); } + @Test + public void testEDQForSysAdmin() throws Exception { + loginSysAdmin(); + + ObjectNode tenantAttr = JacksonUtil.newObjectNode(); + tenantAttr.put("attr", "tenantAttrValue"); + doPost("/api/plugins/telemetry/TENANT/" + tenantId + "/attributes/" + AttributeScope.SERVER_SCOPE, tenantAttr); + + loginTenantAdmin(); + Device tenantDevice = new Device(); + tenantDevice.setName("device " + StringUtils.randomAlphanumeric(10)); + tenantDevice.setType("default"); + tenantDevice = doPost("/api/device", tenantDevice, Device.class); + + loginSysAdmin(); + TenantProfile tenantProfile = doPost("/api/tenantProfile", createTenantProfile("Test tenant profile"), TenantProfile.class); + + ObjectNode tenantProfileAttr = JacksonUtil.newObjectNode(); + tenantProfileAttr.put("attr", "tenantProfileAttrValue"); + doPost("/api/plugins/telemetry/TENANT_PROFILE/" + tenantProfile.getId() + "/attributes/" + AttributeScope.SERVER_SCOPE, tenantProfileAttr); + + // check tenant telemetry is accessible for sysadmin + SingleEntityFilter filter = new SingleEntityFilter(); + filter.setSingleEntity(AliasEntityId.fromEntityId(tenantId)); + EntityDataSortOrder sortOrder = new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC); + EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder); + List entityFields = List.of(new EntityKey(EntityKeyType.ENTITY_FIELD, "name")); + List latestValues = List.of(new EntityKey(EntityKeyType.ATTRIBUTE, "attr")); + EntityDataQuery dataQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null); + + PageData loadedTenants = findByQueryAndCheck(dataQuery, 1); + String retrievedTenantAttr = loadedTenants.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("attr").getValue(); + assertThat(retrievedTenantAttr).isEqualTo("tenantAttrValue"); + + // check tenant profile telemetry is accessible for sysadmin + filter.setSingleEntity(AliasEntityId.fromEntityId(tenantProfile.getId())); + + PageData loadedTenantProfiles = findByQueryAndCheck(dataQuery, 1); + String retrievedProfileAttr = loadedTenantProfiles.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("attr").getValue(); + assertThat(retrievedProfileAttr).isEqualTo("tenantProfileAttrValue"); + + // check other tenant entities are prohibited + filter.setSingleEntity(AliasEntityId.fromEntityId(tenantDevice.getId())); + findByQueryAndCheck(dataQuery, 0); + } + + private TenantProfile createTenantProfile(String name) { + TenantProfile tenantProfile = new TenantProfile(); + tenantProfile.setName(name); + tenantProfile.setDescription(name + " Test"); + TenantProfileData tenantProfileData = new TenantProfileData(); + tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration()); + tenantProfile.setProfileData(tenantProfileData); + tenantProfile.setDefault(false); + tenantProfile.setIsolatedTbRuleEngine(false); + return tenantProfile; + } + @Test public void testTenantCountAlarmsByQuery() throws Exception { loginTenantAdmin(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index b512f353f5..d9deb5d891 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -135,7 +135,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe long startNs = System.nanoTime(); PageData result; - if (edqsService.isApiEnabled() && validForEdqs(query)) { + if (edqsService.isApiEnabled() && validForEdqs(query) && !tenantId.isSysTenantId()) { EdqsRequest request = EdqsRequest.builder() .entityDataQuery(query) .build(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 8f3fe71f35..45515e16bd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -540,7 +540,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { } private String buildPermissionQuery(SqlQueryContext ctx, EntityFilter entityFilter) { - if (ctx.isIgnorePermissionCheck()) { + if (ctx.isIgnorePermissionCheck() || (ctx.getTenantId().isSysTenantId() && + (ctx.getEntityType() == EntityType.TENANT || ctx.getEntityType() == EntityType.TENANT_PROFILE))) { return "1=1"; } switch (entityFilter.getType()) { From 831e95b313844583a3dfdf8d75d44b2f9c5d6447 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 16 Dec 2025 16:08:05 +0200 Subject: [PATCH 02/62] Fixed button changing after dialog closing --- .../lwm2m/lwm2m-bootstrap-config-servers.component.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.ts index 86f01e4cd8..f988d7e6aa 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-bootstrap-config-servers.component.ts @@ -30,7 +30,7 @@ import { TranslateService } from '@ngx-translate/core'; import { DialogService } from '@core/services/dialog.service'; import { MatDialog } from '@angular/material/dialog'; import { Lwm2mBootstrapAddConfigServerDialogComponent } from '@home/components/profile/device/lwm2m/lwm2m-bootstrap-add-config-server-dialog.component'; -import { mergeMap, takeUntil } from 'rxjs/operators'; +import { filter, mergeMap, takeUntil } from 'rxjs/operators'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { Lwm2mSecurityType } from '@shared/models/lwm2m-security-config.models'; @@ -164,10 +164,8 @@ export class Lwm2mBootstrapConfigServersComponent implements OnInit, ControlValu panelClass: ['tb-dialog', 'tb-fullscreen-dialog'] }).afterClosed(); const addServerConfigObs = addDialogObs.pipe( + filter((isBootstrap) => isBootstrap !== null), mergeMap((isBootstrap) => { - if (isBootstrap === null) { - return of(null); - } return this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType(isBootstrap, Lwm2mSecurityType.NO_SEC); }) ); From ecedfab6425e5a2afe522699ed570eb1d5154a69 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Thu, 18 Dec 2025 22:20:18 +0200 Subject: [PATCH 03/62] lwm2m: fix bug: after reboot Lwm2mDeviceProfileTransportConfiguration is null for lts --- .../server/client/LwM2mClientContextImpl.java | 21 ++++-- .../ota/DefaultLwM2MOtaUpdateService.java | 14 ++-- .../uplink/DefaultLwM2mUplinkMsgHandler.java | 69 ++++++------------- 3 files changed, 43 insertions(+), 61 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index 36cf1639db..7b3102a55c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -289,6 +289,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public String getObjectIdByKeyNameFromProfile(LwM2mClient client, String keyName) { Lwm2mDeviceProfileTransportConfiguration profile = getProfile(client.getRegistration()); + if (profile == null) throw new IllegalArgumentException(keyName + " is not configured in the device profile! Device profile is null"); for (Map.Entry entry : profile.getObserveAttr().getKeyName().entrySet()) { String k = entry.getKey(); String v = entry.getValue(); @@ -347,6 +348,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { PowerMode powerMode = lwM2MClient.getPowerMode(); if (powerMode == null) { Lwm2mDeviceProfileTransportConfiguration deviceProfile = getProfile(lwM2MClient.getRegistration()); + if (deviceProfile == null) return null; powerMode = deviceProfile.getClientLwM2mSettings().getPowerMode(); } return powerMode; @@ -377,8 +379,8 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } } } else { - log.warn("Device profile not found! The device profile ID is null. Return Lwm2mDeviceProfileTransportConfiguration with default."); - result = new Lwm2mDeviceProfileTransportConfiguration(); + log.warn("Device profile not found! The device profile ID is null. Return Lwm2mDeviceProfileTransportConfiguration with null."); + result = null; } return result; } @@ -413,6 +415,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { OtherConfiguration profileSettings = null; if (powerMode == null && client.getProfileId() != null) { var clientProfile = getProfile(client.getRegistration()); + if (clientProfile == null) return true; profileSettings = clientProfile.getClientLwM2mSettings(); powerMode = profileSettings.getPowerMode(); } @@ -459,8 +462,10 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { OtherConfiguration profileSettings = null; if (powerMode == null && client.getProfileId() != null) { var clientProfile = getProfile(client.getRegistration()); - profileSettings = clientProfile.getClientLwM2mSettings(); - powerMode = profileSettings.getPowerMode(); + if (clientProfile != null) { + profileSettings = clientProfile.getClientLwM2mSettings(); + powerMode = profileSettings.getPowerMode(); + } } if (powerMode == null || PowerMode.DRX.equals(powerMode)) { client.updateLastUplinkTime(); @@ -515,9 +520,11 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { timeout = client.getEdrxCycle(); } else { var clientProfile = getProfile(client.getRegistration()); - OtherConfiguration clientLwM2mSettings = clientProfile.getClientLwM2mSettings(); - if (PowerMode.E_DRX.equals(clientLwM2mSettings.getPowerMode())) { - timeout = clientLwM2mSettings.getEdrxCycle(); + if (clientProfile != null) { + OtherConfiguration clientLwM2mSettings = clientProfile.getClientLwM2mSettings(); + if (PowerMode.E_DRX.equals(clientLwM2mSettings.getPowerMode())) { + timeout = clientLwM2mSettings.getEdrxCycle(); + } } } if (timeout == null || timeout == 0L) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index 325f1e2280..cc89fb8fd8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -200,7 +200,8 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl attributesToFetch.add(SOFTWARE_URL); } - var clientSettings = clientContext.getProfile(client.getRegistration()).getClientLwM2mSettings(); + var clientProfile = clientContext.getProfile(client.getRegistration()); + var clientSettings = clientProfile != null ? clientProfile.getClientLwM2mSettings() : null; if (clientSettings != null) { initFwStrategy(client, clientSettings); initSwStrategy(client, clientSettings); @@ -532,7 +533,8 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl } else { strategy = info.getDeliveryMethod() == FirmwareDeliveryMethod.PULL.code ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; } - Boolean useObject19ForOtaInfo = clientContext.getProfile(client.getRegistration()).getClientLwM2mSettings().getUseObject19ForOtaInfo(); + var clientProfile = clientContext.getProfile(client.getRegistration()); + Boolean useObject19ForOtaInfo = clientProfile != null ? clientProfile.getClientLwM2mSettings().getUseObject19ForOtaInfo() : null; if (useObject19ForOtaInfo != null && useObject19ForOtaInfo){ sendInfoToObject19ForOta(client, FW_INFO_19_INSTANCE_ID, response, otaPackageId); } @@ -558,7 +560,8 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()); LwM2MSoftwareUpdateStrategy strategy = info.getStrategy(); - Boolean useObject19ForOtaInfo = clientContext.getProfile(client.getRegistration()).getClientLwM2mSettings().getUseObject19ForOtaInfo(); + var clientProfile = clientContext.getProfile(client.getRegistration()); + Boolean useObject19ForOtaInfo = clientProfile != null ? clientProfile.getClientLwM2mSettings().getUseObject19ForOtaInfo() : null; if (useObject19ForOtaInfo != null && useObject19ForOtaInfo){ sendInfoToObject19ForOta(client, SW_INFO_19_INSTANCE_ID, response, otaPackageId); } @@ -647,8 +650,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl LwM2MClientSwOtaInfo info = otaInfoStore.getSw(endpoint); if (info == null) { var profile = clientContext.getProfile(client.getRegistration()); - info = new LwM2MClientSwOtaInfo(endpoint, profile.getClientLwM2mSettings().getSwUpdateResource(), - LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(profile.getClientLwM2mSettings().getSwUpdateStrategy())); + OtherConfiguration clientLwM2mSettings = profile == null ? new OtherConfiguration() : profile.getClientLwM2mSettings(); + info = new LwM2MClientSwOtaInfo(endpoint, clientLwM2mSettings.getSwUpdateResource(), + LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(clientLwM2mSettings.getSwUpdateStrategy())); update(info); } return info; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java index 5e27b936f3..26eb6e83c4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java @@ -418,7 +418,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl }); if (!clients.isEmpty()) { var oldProfile = clientContext.getProfile(clients.get(0).getRegistration()); - this.onDeviceProfileUpdate(clients, oldProfile, deviceProfile); + if (oldProfile != null) this.onDeviceProfileUpdate(clients, oldProfile, deviceProfile); } } catch (Exception e) { log.warn("[{}] failed to update profile: {} [{}]", deviceProfile.getId(), e.getMessage(), deviceProfile); @@ -714,8 +714,10 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl private void onDeviceUpdate(LwM2mClient lwM2MClient, Device device, Optional deviceProfileOpt) { var oldProfile = clientContext.getProfile(lwM2MClient.getRegistration()); - deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceProfileUpdate(Collections.singletonList(lwM2MClient), oldProfile, deviceProfile)); - lwM2MClient.onDeviceUpdate(device, deviceProfileOpt); + if (oldProfile != null) { + deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceProfileUpdate(Collections.singletonList(lwM2MClient), oldProfile, deviceProfile)); + lwM2MClient.onDeviceUpdate(device, deviceProfileOpt); + } } /** @@ -729,45 +731,12 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl Set paths = updateResource.getPaths(); ResultsAddKeyValueProto results = new ResultsAddKeyValueProto(); var profile = clientContext.getProfile(registration); - List resultAttributes = new ArrayList<>(); - Set attributes = profile.getObserveAttr().getAttribute().stream() - .filter(paths::contains) - .collect(Collectors.toSet()); - if (!attributes.isEmpty()){ - attributes.stream() - .map(attr -> this.getKvToThingsBoard(attr, registration)) - .filter(Objects::nonNull) - .forEach(resultAttributes::add); - } - List resultTelemetries = new ArrayList<>(); - Set telemetries = profile.getObserveAttr().getTelemetry().stream() - .filter(paths::contains) - .collect(Collectors.toSet()); - if (!telemetries.isEmpty()){ - telemetries.stream() - .map(telemetry -> this.getKvToThingsBoard(telemetry, registration)) - .filter(Objects::nonNull) - .forEach(resultTelemetries::add); - } - if (resultAttributes.size() > 0) { - results.setResultAttributes(resultAttributes); - } - if (resultTelemetries.size() > 0) { - results.setResultTelemetries(resultTelemetries); - } - return results; - } - - private ResultsAddKeyValueProto getParametersFromProfile(Registration registration, Set path) { - if (!path.isEmpty()) { - ResultsAddKeyValueProto results = new ResultsAddKeyValueProto(); - var profile = clientContext.getProfile(registration); + if (profile != null) { List resultAttributes = new ArrayList<>(); Set attributes = profile.getObserveAttr().getAttribute().stream() - .map(LwM2MTransportUtil::fromVersionedIdToObjectId) - .filter(path::contains) + .filter(paths::contains) .collect(Collectors.toSet()); - if (!attributes.isEmpty()){ + if (!attributes.isEmpty()) { attributes.stream() .map(attr -> this.getKvToThingsBoard(attr, registration)) .filter(Objects::nonNull) @@ -775,10 +744,9 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl } List resultTelemetries = new ArrayList<>(); Set telemetries = profile.getObserveAttr().getTelemetry().stream() - .map(LwM2MTransportUtil::fromVersionedIdToObjectId) - .filter(path::contains) + .filter(paths::contains) .collect(Collectors.toSet()); - if (!telemetries.isEmpty()){ + if (!telemetries.isEmpty()) { telemetries.stream() .map(telemetry -> this.getKvToThingsBoard(telemetry, registration)) .filter(Objects::nonNull) @@ -790,14 +758,15 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl if (resultTelemetries.size() > 0) { results.setResultTelemetries(resultTelemetries); } - return results; } - return null; + return results; } private TransportProtos.KeyValueProto getKvToThingsBoard(String pathIdVer, Registration registration) { LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(registration.getEndpoint()); - Map names = clientContext.getProfile(lwM2MClient.getRegistration()).getObserveAttr().getKeyName(); + var clientProfile = clientContext.getProfile(lwM2MClient.getRegistration()); + if (clientProfile == null) return null; + Map names = clientProfile.getObserveAttr().getKeyName(); if (names != null && names.containsKey(pathIdVer)) { String resourceName = names.get(pathIdVer); if (resourceName != null && !resourceName.isEmpty()) { @@ -887,10 +856,12 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl private void onDeviceProfileUpdate(List clients, Lwm2mDeviceProfileTransportConfiguration oldProfileTransportConfiguration, DeviceProfile deviceProfile) { if (clientContext.profileUpdate(deviceProfile) != null) { var newProfileTransportConfiguration = clientContext.getProfile(clients.get(0).getRegistration()); - ParametersUpdateAnalyzeResult parametersUpdate = getParametersUpdate(oldProfileTransportConfiguration, newProfileTransportConfiguration); - ParametersObserveAnalyzeResult parametersObserve = getParametersObserve(oldProfileTransportConfiguration.getObserveAttr(), newProfileTransportConfiguration.getObserveAttr(), deviceProfile.getId().getId()); - compareAndSetWriteAttributesObservations(clients, parametersUpdate, parametersObserve); - updateValueOta(clients, newProfileTransportConfiguration, oldProfileTransportConfiguration); + if (newProfileTransportConfiguration != null) { + ParametersUpdateAnalyzeResult parametersUpdate = getParametersUpdate(oldProfileTransportConfiguration, newProfileTransportConfiguration); + ParametersObserveAnalyzeResult parametersObserve = getParametersObserve(oldProfileTransportConfiguration.getObserveAttr(), newProfileTransportConfiguration.getObserveAttr(), deviceProfile.getId().getId()); + compareAndSetWriteAttributesObservations(clients, parametersUpdate, parametersObserve); + updateValueOta(clients, newProfileTransportConfiguration, oldProfileTransportConfiguration); + } } } From 295d29fc681a24d5b289e2d8cb561064975ded9e Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 22 Dec 2025 15:16:17 +0200 Subject: [PATCH 04/62] Version set to 4.2.1.1-RC --- application/pom.xml | 2 +- .../service/install/DefaultDatabaseSchemaSettingsService.java | 2 +- common/actor/pom.xml | 2 +- common/cache/pom.xml | 2 +- common/cluster-api/pom.xml | 2 +- common/coap-server/pom.xml | 2 +- common/dao-api/pom.xml | 2 +- common/data/pom.xml | 2 +- common/discovery-api/pom.xml | 2 +- common/edge-api/pom.xml | 2 +- common/edqs/pom.xml | 2 +- common/message/pom.xml | 2 +- common/pom.xml | 2 +- common/proto/pom.xml | 2 +- common/queue/pom.xml | 2 +- common/script/pom.xml | 2 +- common/script/remote-js-client/pom.xml | 2 +- common/script/script-api/pom.xml | 2 +- common/stats/pom.xml | 2 +- common/transport/coap/pom.xml | 2 +- common/transport/http/pom.xml | 2 +- common/transport/lwm2m/pom.xml | 2 +- common/transport/mqtt/pom.xml | 2 +- common/transport/pom.xml | 2 +- common/transport/snmp/pom.xml | 2 +- common/transport/transport-api/pom.xml | 2 +- common/util/pom.xml | 2 +- common/version-control/pom.xml | 2 +- dao/pom.xml | 2 +- edqs/pom.xml | 2 +- monitoring/pom.xml | 2 +- msa/black-box-tests/pom.xml | 2 +- msa/edqs/pom.xml | 2 +- msa/js-executor/package.json | 2 +- msa/js-executor/pom.xml | 2 +- msa/monitoring/pom.xml | 2 +- msa/pom.xml | 2 +- msa/tb-node/pom.xml | 2 +- msa/tb/pom.xml | 2 +- msa/transport/coap/pom.xml | 2 +- msa/transport/http/pom.xml | 2 +- msa/transport/lwm2m/pom.xml | 2 +- msa/transport/mqtt/pom.xml | 2 +- msa/transport/pom.xml | 2 +- msa/transport/snmp/pom.xml | 2 +- msa/vc-executor-docker/pom.xml | 2 +- msa/vc-executor/pom.xml | 2 +- msa/web-ui/package.json | 2 +- msa/web-ui/pom.xml | 2 +- netty-mqtt/pom.xml | 4 ++-- pom.xml | 2 +- rest-client/pom.xml | 2 +- rule-engine/pom.xml | 2 +- rule-engine/rule-engine-api/pom.xml | 2 +- rule-engine/rule-engine-components/pom.xml | 2 +- tools/pom.xml | 2 +- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/lwm2m/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- transport/pom.xml | 2 +- transport/snmp/pom.xml | 2 +- ui-ngx/package.json | 2 +- ui-ngx/pom.xml | 2 +- 64 files changed, 65 insertions(+), 65 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 9c683cbd25..47772dfa97 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard application diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java index 1917b4e22d..44aa14b782 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java @@ -30,7 +30,7 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti // This list should include all versions that are compatible for the upgrade in 4 digits format (like 4.2.0.0, etc.). // The compatibility cycle usually breaks when we have some scripts written in Java that may not work after a new release. - private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.2.0.0"); + private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.2.0.0", "4.2.1.0"); private final ProjectInfo projectInfo; private final JdbcTemplate jdbcTemplate; diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 4149fe2ad9..6411934f7d 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index 6831156fde..a43009b6e1 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index 78ef7980e8..9a75a6b0c3 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index b24fd98f5d..1950a9ecc2 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 555b4a54bb..8b0ad31e23 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index d81566f521..a574a6ed07 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/discovery-api/pom.xml b/common/discovery-api/pom.xml index cb5238a664..9e65abf10a 100644 --- a/common/discovery-api/pom.xml +++ b/common/discovery-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index 2b7db0cbd3..208e1e7b55 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/edqs/pom.xml b/common/edqs/pom.xml index cc5c5db14e..677f5ad5ae 100644 --- a/common/edqs/pom.xml +++ b/common/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index c84a4fde2d..f70bc260cf 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index f45a2bdd28..5467f452d2 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard common diff --git a/common/proto/pom.xml b/common/proto/pom.xml index 30d4b0bc68..bab2f8d12b 100644 --- a/common/proto/pom.xml +++ b/common/proto/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index e6ab3a4704..19368c1345 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/script/pom.xml b/common/script/pom.xml index ab5ac032c7..4b7a812737 100644 --- a/common/script/pom.xml +++ b/common/script/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml index 1a3e00a88c..45d4698efe 100644 --- a/common/script/remote-js-client/pom.xml +++ b/common/script/remote-js-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1-RC + 4.2.1.1-RC script org.thingsboard.common.script diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml index 316f823a44..3bc2168dfe 100644 --- a/common/script/script-api/pom.xml +++ b/common/script/script-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1-RC + 4.2.1.1-RC script org.thingsboard.common.script diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 4bf24411dc..66011657d8 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index be3c62c057..e9d5dcfc1a 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index a136079cb5..caff518fe2 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 904e359bcc..2bec8f7bcb 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 45f9b76af7..18ff4e96c3 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index f786bcdd0e..feaef2bcce 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index d3b2c46d69..da5093349d 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 4.2.1-RC + 4.2.1.1-RC transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index a3fee460e1..1cb128b439 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index ebedf5dea3..6509f154ad 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index b6a0ea1d2e..939d0d47ad 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index a1843959df..5044c0edfb 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard dao diff --git a/edqs/pom.xml b/edqs/pom.xml index d648d9d786..ef91e0bed4 100644 --- a/edqs/pom.xml +++ b/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard edqs diff --git a/monitoring/pom.xml b/monitoring/pom.xml index 77874c5ac9..3fb4ba5314 100644 --- a/monitoring/pom.xml +++ b/monitoring/pom.xml @@ -21,7 +21,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index fed33f0002..6800c0c4cf 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.2.1-RC + 4.2.1.1-RC msa org.thingsboard.msa diff --git a/msa/edqs/pom.xml b/msa/edqs/pom.xml index c5be28e7bb..62374eb2bc 100644 --- a/msa/edqs/pom.xml +++ b/msa/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC msa org.thingsboard.msa diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index 88fc7924bf..3f4370b642 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-js-executor", "private": true, - "version": "4.2.1", + "version": "4.2.1.1", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 651b142160..3aaf380e2f 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC msa org.thingsboard.msa diff --git a/msa/monitoring/pom.xml b/msa/monitoring/pom.xml index abd12ed9e0..b81abce27d 100644 --- a/msa/monitoring/pom.xml +++ b/msa/monitoring/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC msa diff --git a/msa/pom.xml b/msa/pom.xml index 24cc67f4bf..48c83f6ee1 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 0103bf9352..e473e1dfdf 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 28e1366a73..9db071c9ae 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 8aefc94122..098e3263b8 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index a1481203de..3acbeb7a2d 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index 7af2b16588..bc6d0d004e 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index 176dca19eb..ef20c96f26 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index afbab0c450..76cec909da 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index 553ed8bb37..348880019c 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 4.2.1-RC + 4.2.1.1-RC org.thingsboard.msa.transport diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml index d0a291e246..b1c11efe0a 100644 --- a/msa/vc-executor-docker/pom.xml +++ b/msa/vc-executor-docker/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC msa org.thingsboard.msa diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml index cf777c4c96..a59a8aa823 100644 --- a/msa/vc-executor/pom.xml +++ b/msa/vc-executor/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.2.1-RC + 4.2.1.1-RC msa org.thingsboard.msa diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index e8d4eedd5d..97d69546bc 100644 --- a/msa/web-ui/package.json +++ b/msa/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-web-ui", "private": true, - "version": "4.2.1", + "version": "4.2.1.1", "description": "ThingsBoard Web UI Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index a56d0ca9d9..70200eddb3 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 08f2a55f4a..c0743024ea 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard netty-mqtt - 4.2.1-RC + 4.2.1.1-RC jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 4c5b2291be..7f8ef8074a 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 4.2.1-RC + 4.2.1.1-RC pom Thingsboard diff --git a/rest-client/pom.xml b/rest-client/pom.xml index e6e82da954..eddcd84d9a 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard rest-client diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 8a9ad96d95..99372b9703 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 0ed5a8cdbf..c52fb92594 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 90e6a87e52..f582f1277a 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC rule-engine org.thingsboard.rule-engine diff --git a/tools/pom.xml b/tools/pom.xml index 981c4804c8..783dc1ee73 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 8ae86d381f..f43d31e35b 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 487e1de186..7e55ae3db5 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.transport diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index 30effe6e42..f64f8b4aa4 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index a609e8d002..0bcf68cbb8 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index c6a82349f5..1d3755a766 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index 1894f09787..5ea39a4066 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.2.1-RC + 4.2.1.1-RC transport diff --git a/ui-ngx/package.json b/ui-ngx/package.json index e993271fa8..74b4bbbb9c 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -1,6 +1,6 @@ { "name": "thingsboard", - "version": "4.2.1", + "version": "4.2.1.1", "scripts": { "ng": "ng", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open", diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index 905a1a8aca..d5bd48de78 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1-RC + 4.2.1.1-RC thingsboard org.thingsboard From 68f432b581e27bf94ec9c401bf9239f3b70ef32c Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 22 Dec 2025 16:51:46 +0200 Subject: [PATCH 05/62] Set default docker base image to thingsboard/openjdk25:trixie-slim --- msa/pom.xml | 2 +- msa/tb/docker-cassandra/Dockerfile | 2 +- msa/tb/docker-postgres/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/msa/pom.xml b/msa/pom.xml index 48c83f6ee1..70a907292b 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -32,7 +32,7 @@ ${basedir}/.. thingsboard - thingsboard/openjdk17:bookworm-slim + thingsboard/openjdk25:trixie-slim true true 1.4.13 diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index 20a0d8cf7a..a915e16ba2 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bookworm-slim +FROM ${docker.base.image} ENV PG_MAJOR=16 diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile index 7c66d626c7..f9898e955e 100644 --- a/msa/tb/docker-postgres/Dockerfile +++ b/msa/tb/docker-postgres/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bookworm-slim +FROM ${docker.base.image} ENV PG_MAJOR 12 From 9eedf7476303e8049530417cfa1e4be85b8f26a7 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 22 Dec 2025 16:58:18 +0200 Subject: [PATCH 06/62] Revert docker base openjdk25 for monolith images --- msa/tb/docker-cassandra/Dockerfile | 2 +- msa/tb/docker-postgres/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index a915e16ba2..20a0d8cf7a 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM ${docker.base.image} +FROM thingsboard/openjdk17:bookworm-slim ENV PG_MAJOR=16 diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile index f9898e955e..7c66d626c7 100644 --- a/msa/tb/docker-postgres/Dockerfile +++ b/msa/tb/docker-postgres/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM ${docker.base.image} +FROM thingsboard/openjdk17:bookworm-slim ENV PG_MAJOR 12 From dbbd97cbccea827b5520b6f01c7fed9eb9a86e02 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Mon, 22 Dec 2025 17:15:42 +0200 Subject: [PATCH 07/62] Edge mergeAndFilterDownlinkDuplicates - handle cases when no TS in attrbiutes body --- .../service/edge/EdgeMsgConstructorUtils.java | 35 +++++++++++-------- .../server/edge/DeviceEdgeTest.java | 5 +-- .../edge/EdgeMsgConstructorUtilsTest.java | 25 +++++++++++++ 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java index e1c7f1198c..33084f98fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java @@ -560,7 +560,7 @@ public class EdgeMsgConstructorUtils { .setEntityIdMSB(entityId.getId().getMostSignificantBits()) .setEntityIdLSB(entityId.getId().getLeastSignificantBits()) .setEntityType(entityId.getEntityType().name()); - long ts = getTs(entityData.getAsJsonObject()); + long ts = extractTs(entityData.getAsJsonObject()); switch (actionType) { case TIMESERIES_UPDATED: try { @@ -613,8 +613,8 @@ public class EdgeMsgConstructorUtils { return builder.build(); } - private static long getTs(JsonObject data) { - if (data.get("ts") != null && !data.get("ts").isJsonNull()) { + private static long extractTs(JsonObject data) { + if (data.has("ts") && data.get("ts").isJsonPrimitive()) { return data.getAsJsonPrimitive("ts").getAsLong(); } return System.currentTimeMillis(); @@ -740,7 +740,7 @@ public class EdgeMsgConstructorUtils { result.sort(Comparator.comparingLong(EdgeEvent::getSeqId)); return result; } catch (Exception e) { - log.warn("Can't merge downlink duplicates, edgeEvents [{}]", edgeEvents, e); + log.info("Can't merge downlink duplicates. Sending downlinks without merge. Original edgeEvents [{}]", edgeEvents, e); return edgeEvents; } } @@ -751,6 +751,9 @@ public class EdgeMsgConstructorUtils { } String bodyStr = JacksonUtil.toString(body); var jsonObject = JsonParser.parseString(bodyStr).getAsJsonObject(); + if (!jsonObject.has("ts")) { + return new AttrsTs(0L, List.of()); + } long ts = jsonObject.get("ts").getAsLong(); var kv = jsonObject.getAsJsonObject("kv"); List attrs = JsonConverter.convertToAttributes( @@ -761,22 +764,24 @@ public class EdgeMsgConstructorUtils { } private static JsonNode filterAttributesBody(JsonNode body, Map latestByKey) { - if (body == null || latestByKey == null || latestByKey.isEmpty()) { + if (body == null) { return null; } String bodyStr = JacksonUtil.toString(body); JsonObject jsonObject = JsonParser.parseString(bodyStr).getAsJsonObject(); - long ts = jsonObject.get("ts").getAsLong(); - JsonObject kv = jsonObject.getAsJsonObject("kv"); - for (Iterator> it = kv.entrySet().iterator(); it.hasNext(); ) { - Map.Entry e = it.next(); - Long latestTs = latestByKey.get(e.getKey()); - if (latestTs == null || !latestTs.equals(ts)) { - it.remove(); + if (jsonObject.has("ts") && latestByKey != null && !latestByKey.isEmpty()) { + long ts = jsonObject.get("ts").getAsLong(); + JsonObject kv = jsonObject.getAsJsonObject("kv"); + for (Iterator> it = kv.entrySet().iterator(); it.hasNext(); ) { + Map.Entry e = it.next(); + Long latestTs = latestByKey.get(e.getKey()); + if (latestTs == null || !latestTs.equals(ts)) { + it.remove(); + } + } + if (kv.isEmpty()) { + return null; } - } - if (kv.isEmpty()) { - return null; } return JacksonUtil.toJsonNode(jsonObject.toString()); } diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java index 380dec8015..5c3f43b451 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java @@ -49,8 +49,6 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; @@ -671,8 +669,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest { .atMost(10, TimeUnit.SECONDS) .until(() -> { String urlTemplate = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/keys/attributes/" + scope; - List actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() { - }); + List actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() {}); return actualKeys != null && !actualKeys.isEmpty() && actualKeys.contains(expectedKey); }); diff --git a/application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java b/application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java index ebb8305871..1e268e3bcb 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java @@ -230,10 +230,35 @@ public class EdgeMsgConstructorUtilsTest { Assertions.assertEquals(8, getIntValue(assetMergedAttrAD.getBody(), "d")); } + @Test + public void testMergeDownlinkDuplicates_attrBodyHasNoTs_returnOriginalList() { + UUID deviceId = UUID.randomUUID(); + TenantId tenantId = TenantId.fromUUID(UUID.randomUUID()); + + var deviceAttrUpdate1 = createEdgeEvent(tenantId, 1, EdgeEventActionType.ATTRIBUTES_UPDATED, + deviceId, EdgeEventType.DEVICE, createAttrBodyWithoutTs("{\"a\":1,\"b\":1,\"d\":1}")); + var deviceAttrUpdate2 = createEdgeEvent(tenantId, 2, EdgeEventActionType.ATTRIBUTES_UPDATED, + deviceId, EdgeEventType.DEVICE, createAttrBodyWithoutTs("{\"a\":2,\"b\":2,\"c\":2}")); + var deviceAttrUpdate3 = createEdgeEvent(tenantId, 3, EdgeEventActionType.ATTRIBUTES_UPDATED, + deviceId, EdgeEventType.DEVICE, createAttrBodyWithoutTs("{\"a\":3,\"d\":3}")); + + List input = List.of(deviceAttrUpdate1, deviceAttrUpdate2, deviceAttrUpdate3); + List merged = EdgeMsgConstructorUtils.mergeAndFilterDownlinkDuplicates(input); + + Assertions.assertEquals(3, merged.size()); + Assertions.assertEquals(deviceAttrUpdate1, merged.get(0)); + Assertions.assertEquals(deviceAttrUpdate2, merged.get(1)); + Assertions.assertEquals(deviceAttrUpdate3, merged.get(2)); + } + private Integer getIntValue(JsonNode body, String key) { return body.get("kv").get(key) != null ? body.get("kv").get(key).asInt() : null; } + private static JsonNode createAttrBodyWithoutTs(String kvJson) { + return JacksonUtil.toJsonNode("{\"kv\":" + kvJson + "}"); + } + private static JsonNode createAttrBody(long ts, String kvJson) { return JacksonUtil.toJsonNode("{\"ts\":" + ts + ",\"kv\":" + kvJson + "}"); } From 028083da9d83a19588da79db1cc4f7f6cee99ec5 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Tue, 23 Dec 2025 10:48:21 +0200 Subject: [PATCH 08/62] Add LTS docker tag build config --- msa/pom.xml | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/msa/pom.xml b/msa/pom.xml index 70a907292b..5eff7cc06d 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -125,6 +125,48 @@ + + lts + + + lts + + + + 4.2.1-latest + + + + + org.codehaus.mojo + exec-maven-plugin + + + push-lts-docker-amd-arm-images + ${docker.push-arm-amd-image.phase} + + exec + + + docker + ${project.build.directory} + + buildx + build + -t + ${docker.repo}/${docker.name}:${docker.lts.tag} + --platform=linux/amd64,linux/arm64 + -o + type=registry + . + + + + + + + + From f704fcc6a8e7656adadf648376aef84fec87d513 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Tue, 23 Dec 2025 12:31:52 +0200 Subject: [PATCH 09/62] Separate push-lts-docker-amd-arm-images profile --- msa/pom.xml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/msa/pom.xml b/msa/pom.xml index 5eff7cc06d..66e92678ce 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -126,10 +126,10 @@ - lts + push-lts-docker-amd-arm-images - lts + push-lts-docker-amd-arm-images @@ -141,6 +141,27 @@ org.codehaus.mojo exec-maven-plugin + + push-version-docker-amd-arm-images + ${docker.push-arm-amd-image.phase} + + exec + + + docker + ${project.build.directory} + + buildx + build + -t + ${docker.repo}/${docker.name}:${project.version} + --platform=linux/amd64,linux/arm64 + -o + type=registry + . + + + push-lts-docker-amd-arm-images ${docker.push-arm-amd-image.phase} From 4a30d778782f3aa0bf5d510bced1c0fba4e0928f Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 23 Dec 2025 12:39:20 +0200 Subject: [PATCH 10/62] Added translations for severity in alarm comments --- .../alarm/alarm-comment.component.ts | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts index 800fdf2371..220a49210d 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts @@ -32,13 +32,15 @@ import { AlarmCommentInfo, AlarmCommentType, AlarmMessage, + AlarmSeverity, + alarmSeverityTranslations, getUserDisplayName } from '@shared/models/alarm.models'; import { UtilsService } from '@core/services/utils.service'; import { EntityType } from '@shared/models/entity-type.models'; import { DatePipe } from '@angular/common'; import { ImportExportService } from '@shared/import-export/import-export.service'; -import { isNotEmptyStr } from '@core/utils'; +import { deepClone, isNotEmptyStr } from '@core/utils'; interface AlarmCommentsDisplayData { commentId?: string; @@ -152,9 +154,24 @@ export class AlarmCommentComponent implements OnInit { private parseSystemComment(alarm: AlarmCommentInfo): string { const subTypeKey = alarm.comment?.subtype; + const alarmComment = deepClone(alarm.comment); if (subTypeKey && AlarmMessage[subTypeKey]) { const translationKey = AlarmMessage[subTypeKey]; - return this.translate.instant(translationKey, alarm.comment); + if (alarmComment?.newSeverity) { + const newSeverityKey = + (alarmSeverityTranslations.has(alarmComment.newSeverity) + ? alarmSeverityTranslations.get(alarmComment.newSeverity) + : alarmComment.newSeverity) as AlarmSeverity; + alarmComment.newSeverity = this.translate.instant(newSeverityKey); + } + if (alarmComment?.oldSeverity) { + const oldSeverityKey = + (alarmSeverityTranslations.has(alarmComment.oldSeverity) + ? alarmSeverityTranslations.get(alarmComment.oldSeverity) + : alarmComment.oldSeverity) as AlarmSeverity; + alarmComment.oldSeverity = this.translate.instant(oldSeverityKey); + } + return this.translate.instant(translationKey, alarmComment); } return alarm.comment.text; } From ea29bdaf4ad9824d5e6f7bdb8daf9001e80462b2 Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Tue, 23 Dec 2025 12:40:22 +0200 Subject: [PATCH 11/62] UI: Enhancement for alarm severity colors --- .../notification/notification.component.html | 2 +- .../notification/notification.component.ts | 10 +++++++--- ui-ngx/src/app/shared/models/alarm.models.ts | 20 ++++++++++++++----- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/shared/components/notification/notification.component.html b/ui-ngx/src/app/shared/components/notification/notification.component.html index 674a074a85..0a667ff334 100644 --- a/ui-ngx/src/app/shared/components/notification/notification.component.html +++ b/ui-ngx/src/app/shared/components/notification/notification.component.html @@ -42,7 +42,7 @@ check_circle_outline
+ [style.background-color]="alarmColorSeverityBackground()"> {{alarmSeverityTranslations.get(notification.info.alarmSeverity) | translate}} diff --git a/ui-ngx/src/app/shared/components/notification/notification.component.ts b/ui-ngx/src/app/shared/components/notification/notification.component.ts index 204c9e3230..9e7398e457 100644 --- a/ui-ngx/src/app/shared/components/notification/notification.component.ts +++ b/ui-ngx/src/app/shared/components/notification/notification.component.ts @@ -24,7 +24,11 @@ import { } from '@shared/models/notification.models'; import { UtilsService } from '@core/services/utils.service'; import { Router } from '@angular/router'; -import { alarmSeverityColors, alarmSeverityTranslations } from '@shared/models/alarm.models'; +import { + alarmSeverityBackgroundColors, + alarmSeverityColors, + alarmSeverityTranslations +} from '@shared/models/alarm.models'; import tinycolor from 'tinycolor2'; import { StateObject } from '@core/api/widget-api.models'; import { objToBase64URI } from '@core/utils'; @@ -133,8 +137,8 @@ export class NotificationComponent implements OnInit { } } - alarmColorSeverity(alpha: number) { - return tinycolor(alarmSeverityColors.get(this.notification.info.alarmSeverity)).setAlpha(alpha).toRgbString(); + alarmColorSeverityBackground() { + return alarmSeverityBackgroundColors.get(this.notification.info.alarmSeverity); } notificationColor(): string { diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts index fda79a0154..76199faf2c 100644 --- a/ui-ngx/src/app/shared/models/alarm.models.ts +++ b/ui-ngx/src/app/shared/models/alarm.models.ts @@ -88,11 +88,21 @@ export const alarmSearchStatusTranslations = new Map( export const alarmSeverityColors = new Map( [ - [AlarmSeverity.CRITICAL, '#D12730'], - [AlarmSeverity.MAJOR, '#FEAC0C'], - [AlarmSeverity.MINOR, '#F2DA05'], - [AlarmSeverity.WARNING, '#F66716'], - [AlarmSeverity.INDETERMINATE, '#00000061'] + [AlarmSeverity.CRITICAL, 'var(--tb-alarm-severity-critical, rgb(209, 39, 48))'], + [AlarmSeverity.MAJOR, 'var(--tb-alarm-severity-major, rgb(246, 103, 22))'], + [AlarmSeverity.MINOR, 'var(--tb-alarm-severity-minor, rgb(250, 164, 5))'], + [AlarmSeverity.WARNING, 'var(--tb-alarm-severity-warning, rgb(242, 218, 5))'], + [AlarmSeverity.INDETERMINATE, 'var(--tb-alarm-severity-indeterminate, rgba(0, 0, 0, 0.38))'] + ] +); + +export const alarmSeverityBackgroundColors = new Map( + [ + [AlarmSeverity.CRITICAL, `var(--tb-alarm-severity-critical-bg, rgba(209, 39, 48, 0.06))`], + [AlarmSeverity.MAJOR, 'var(--tb-alarm-severity-major-bg, rgba(246, 103, 22, 0.06))'], + [AlarmSeverity.MINOR, 'var(--tb-alarm-severity-minor-bg, rgba(250, 164, 5, 0.06))'], + [AlarmSeverity.WARNING, 'var(--tb-alarm-severity-warning-bg, rgba(242, 218, 5, 0.06))'], + [AlarmSeverity.INDETERMINATE, 'var(--tb-alarm-severity-indeterminate-bg, rgba(0, 0, 0, 0.06))'] ] ); From 73a09f79d21ad036f27f91bce025af35ccd53103 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 23 Dec 2025 13:05:00 +0200 Subject: [PATCH 12/62] Simplified logic --- .../home/components/alarm/alarm-comment.component.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts index 220a49210d..6d426cdcd0 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts @@ -154,22 +154,20 @@ export class AlarmCommentComponent implements OnInit { private parseSystemComment(alarm: AlarmCommentInfo): string { const subTypeKey = alarm.comment?.subtype; - const alarmComment = deepClone(alarm.comment); if (subTypeKey && AlarmMessage[subTypeKey]) { + const alarmComment = deepClone(alarm.comment); const translationKey = AlarmMessage[subTypeKey]; if (alarmComment?.newSeverity) { - const newSeverityKey = + alarmComment.newSeverity = (alarmSeverityTranslations.has(alarmComment.newSeverity) - ? alarmSeverityTranslations.get(alarmComment.newSeverity) + ? this.translate.instant(alarmSeverityTranslations.get(alarmComment.newSeverity)) : alarmComment.newSeverity) as AlarmSeverity; - alarmComment.newSeverity = this.translate.instant(newSeverityKey); } if (alarmComment?.oldSeverity) { - const oldSeverityKey = + alarmComment.oldSeverity = (alarmSeverityTranslations.has(alarmComment.oldSeverity) - ? alarmSeverityTranslations.get(alarmComment.oldSeverity) + ? this.translate.instant(alarmSeverityTranslations.get(alarmComment.oldSeverity)) : alarmComment.oldSeverity) as AlarmSeverity; - alarmComment.oldSeverity = this.translate.instant(oldSeverityKey); } return this.translate.instant(translationKey, alarmComment); } From 8f90c7fb659e7c5e101b0692e0079d27c1631dca Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Tue, 23 Dec 2025 14:47:08 +0200 Subject: [PATCH 13/62] added help pages for related entities aggregation scripts --- ...culated-field-metrics-panel.component.html | 2 +- .../en_US/calculated-field/expression_fn.md | 9 ++- .../calculated-field/filter_expression_fn.md | 64 ++++++++++++++- .../calculated-field/map_expression_fn.md | 79 +++++++++++++++++++ 4 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 ui-ngx/src/assets/help/en_US/calculated-field/map_expression_fn.md diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.html index f86c15ba73..178fedbecd 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.html @@ -170,7 +170,7 @@ [highlightRules]="highlightRules" [editorCompleter]="editorCompleter" [helpPopupStyle]="{ width: '1200px' }" - helpId="calculated-field/expression_fn"> + helpId="calculated-field/map_expression_fn">
{{ 'api-usage.tbel' | translate }}
diff --git a/ui-ngx/src/assets/help/en_US/calculated-field/expression_fn.md b/ui-ngx/src/assets/help/en_US/calculated-field/expression_fn.md index 1d45dea3c8..6129dac351 100644 --- a/ui-ngx/src/assets/help/en_US/calculated-field/expression_fn.md +++ b/ui-ngx/src/assets/help/en_US/calculated-field/expression_fn.md @@ -11,12 +11,16 @@ function calculate(ctx, arg1, arg2, ...): object | object[] ### Supported Arguments +Arguments are passed to the function by **name** defined in the calculated field configuration. + There are three types of arguments supported in the calculated field configuration: #### Attribute and Latest Telemetry Arguments These arguments are single values and may be of type: boolean, int64 (long), double, string, or JSON. +#### Direct argument access via **``** + **Example: Convert Temperature from Fahrenheit to Celsius** ```javascript @@ -26,7 +30,9 @@ return { } ``` -Alternatively, using `ctx` to access the argument as an object: +#### Accessing argument via **`ctx.args.`** + +In addition to direct access, arguments can be accessed via the `ctx.args.` object, which includes both the `value` of an argument and its timestamp as `ts`: ```json { @@ -37,7 +43,6 @@ Alternatively, using `ctx` to access the argument as an object: } ``` -You may notice that the object includes both the `value` of an argument and its timestamp as `ts`. Let's modify the function that converts Fahrenheit to Celsius to also return the timestamp information: ```javascript diff --git a/ui-ngx/src/assets/help/en_US/calculated-field/filter_expression_fn.md b/ui-ngx/src/assets/help/en_US/calculated-field/filter_expression_fn.md index ce8ca22f51..22316fe418 100644 --- a/ui-ngx/src/assets/help/en_US/calculated-field/filter_expression_fn.md +++ b/ui-ngx/src/assets/help/en_US/calculated-field/filter_expression_fn.md @@ -1,10 +1,70 @@ ## Calculated Field TBEL Filter Function -The **filter()** function is a user-defined script that enables custom calculations using [TBEL](${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/tbel/) on telemetry and attribute data. +The **filter()** function is a [TBEL](${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/tbel/) script used in aggregation metrics of a calculated field. + It receives arguments configured in the calculated field setup, along with an additional `ctx` object that stores `latestTs` and provides access to all arguments. +It allows you to include or exclude related entities from aggregation based on their telemetry or attribute values. + +The filter is evaluated per related entity before the aggregation function is applied. + ### Function Signature ```javascript -function calculate(ctx, arg1, arg2, ...): boolean +function filter(ctx, arg1, arg2, ...): boolean +``` + +### Supported Arguments + +Arguments are passed to the function by **name** defined in the calculated field configuration. + +There are two types of arguments supported in the calculated field configuration: **Attribute and Latest Telemetry Arguments** + +These arguments are single values and may be of type: boolean, int64 (long), double, string, or JSON. + +#### Direct argument access via **``** + +**Example: Count free parking spaces** + +**Goal**: Include only parking spaces that are active and not occupied. + +```javascript +return active == true && occupied == false; +``` + +Only entities that satisfy this condition will be included in the aggregation. + +#### Accessing argument via **`ctx.args.`** + +In addition to direct access, arguments can be accessed via the `ctx.args.` object, which includes both the `value` of an argument and its timestamp as `ts`: + +```json +{ + "consumption": { + "ts": 1740644656669, + "value": 542.6 + } +} +``` + +The `ctx.latestTs` property represents the latest timestamp across all related entities and their arguments participating in the aggregation. + +**Example: Calculate the total consumption across multiple related pumps** + +**Scenario**: Each pump reports consumption telemetry approximately every 10 minutes, but reporting times may vary due to network delays (up to ~30 seconds). +To avoid counting outdated values, only recently updated telemetry should be included. + +**Goal**: Include only pumps whose consumption value was reported within 1 minute of the latest timestamp. + +```javascript +var ONE_MINUTE = 60 * 1000; +return (ctx.latestTs - ctx.args.consumption.ts) <= ONE_MINUTE; ``` + +### Function return format + +The function **must** return a boolean: +- `true` → include entity in aggregation +- `false` → exclude entity from aggregation + +Any other return type is considered invalid. diff --git a/ui-ngx/src/assets/help/en_US/calculated-field/map_expression_fn.md b/ui-ngx/src/assets/help/en_US/calculated-field/map_expression_fn.md new file mode 100644 index 0000000000..41ccf3540b --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/calculated-field/map_expression_fn.md @@ -0,0 +1,79 @@ +## Calculated Field TBEL Map Function + +The **map()** function is a [TBEL](${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/tbel/) script used in aggregation metrics of a calculated field. + +It determines the value applied by each related entity to the aggregation. + +The function receives arguments configured in the calculated field setup, along with an additional `ctx` object that stores `latestTs` and provides access to all arguments. + +The function is evaluated **per related entity**, after filtering is applied. + +### Function Signature + +```javascript +function map(ctx, arg1, arg2, ...): number | boolean | string +``` + +### Supported Arguments + +Arguments are passed to the function by **name** defined in the calculated field configuration. + +There are two types of arguments supported in the calculated field configuration: **Attribute and Latest Telemetry Arguments** + +These arguments are single values and may be of type: boolean, int64 (long), double, string, or JSON. + +#### Direct argument access via **``** + +**Example: Calculate average temperature across sensors** + +**Scenario**: Multiple related sensors report temperature in Fahrenheit. You want to aggregate temperature values, but the aggregation must be performed in Celsius. + +**Goal**: Convert temperature to Celsius before aggregation + +```javascript +var temperatureC = (temperature - 32) / 1.8; +return toFixed(temperatureC, 2); +``` + +Instead of creating a separate calculated field for conversion, the transformation is performed per entity, before aggregation. + +#### Accessing argument via **`ctx.args.`** + +In addition to direct access, arguments can be accessed via the `ctx.args.` object, which includes both the `value` of an argument and its timestamp as `ts`: + +```json +{ + "temperature": { + "ts": 1740644656761, + "value": 33.6 + } +} +``` + +The `ctx.latestTs` property represents the latest timestamp across all related entities and their arguments participating in the aggregation. + +**Example: Calculate average temperature** + +**Scenario**: Each sensor reports temperature approximately every 10 minutes, but reporting times may vary due to network delays (up to ~30 seconds). +If a temperature value is outdated, you want the entity to remain part of the aggregation, but contribute a default value instead of a stale one. + +**Goal**: Return a default value when the temperature was not reported within 1 minute of the latest aggregation timestamp. + +```javascript +var ONE_MINUTE = 60 * 1000; +if ((ctx.latestTs - ctx.args.temperature.ts) > ONE_MINUTE) { + return 0; +}; +return temperature; +``` + +### Function return format + +The returned value is passed directly to the aggregation engine: + +| Aggregation function | Expected return value | +|--------------------------------|-----------------------| +| Count, Count unique | any value | +| Sum, Average, Minimum, Maximum | number | + +Returning a value of an incompatible type may result in the entity being ignored or the aggregation producing incorrect results. From 1ff694f0fd0f233012ac3e2093835c59f4d74230 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 23 Dec 2025 15:21:41 +0200 Subject: [PATCH 14/62] Revert to Java 17 due to fonts issue --- msa/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msa/pom.xml b/msa/pom.xml index 66e92678ce..a210c45eef 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -32,7 +32,7 @@ ${basedir}/.. thingsboard - thingsboard/openjdk25:trixie-slim + thingsboard/openjdk17:bookworm-slim true true 1.4.13 From cff9286290c507e8f2e953f85b11ffaecdc5ae0f Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Tue, 23 Dec 2025 15:49:02 +0200 Subject: [PATCH 15/62] Add optional latest tag push for LTS --- msa/pom.xml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/msa/pom.xml b/msa/pom.xml index 66e92678ce..6af7521df6 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -134,6 +134,7 @@ 4.2.1-latest + false @@ -141,6 +142,28 @@ org.codehaus.mojo exec-maven-plugin + + push-latest-docker-amd-arm-images + ${docker.push-arm-amd-image.phase} + + exec + + + ${docker.skip.latest.tag} + docker + ${project.build.directory} + + buildx + build + -t + ${docker.repo}/${docker.name}:latest + --platform=linux/amd64,linux/arm64 + -o + type=registry + . + + + push-version-docker-amd-arm-images ${docker.push-arm-amd-image.phase} From 0ea0dcadc04cfc27913cf330444581170341ec10 Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Tue, 23 Dec 2025 18:41:40 +0200 Subject: [PATCH 16/62] UI: overide alarm severity colors from widget and dashboard css settings --- ui-ngx/src/app/core/services/utils.service.ts | 4 ++-- .../components/dashboard-page/dashboard-page.component.ts | 1 + .../home/components/dashboard-page/dashboard-page.models.ts | 1 + .../widget/lib/alarm/alarms-table-widget.component.ts | 2 +- .../home/components/widget/widget-container.component.ts | 4 ++-- ui-ngx/src/app/modules/home/models/widget-component.models.ts | 2 ++ 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index a57e3480b1..045725271c 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -454,11 +454,11 @@ export class UtilsService { return base64toObj(b64Encoded); } - public applyCssToElement(renderer: Renderer2, element: any, cssClassPrefix: string, css: string): string { + public applyCssToElement(renderer: Renderer2, element: any, cssClassPrefix: string, css: string, addTbDefaultClass: boolean = false): string { const cssParser = new cssjs(); cssParser.testMode = false; const cssClass = `${cssClassPrefix}-${guid()}`; - cssParser.cssPreviewNamespace = cssClass; + cssParser.cssPreviewNamespace = addTbDefaultClass ? 'tb-default .' + cssClass : cssClass; cssParser.createStyleElement(cssClass, css); renderer.addClass(element, cssClass); return cssClass; diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index d0c3325c72..090822a146 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -548,6 +548,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC const cssParser = new cssjs(); cssParser.testMode = false; this.dashboardPageClass = 'tb-dashboard-page-css-' + guid(); + this.dashboardCtx.dashboardCssClass = this.dashboardPageClass; cssParser.cssPreviewNamespace = 'tb-default .' + this.dashboardPageClass; cssParser.createStyleElement(this.dashboardPageClass, cssString); } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.models.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.models.ts index a0fa516374..fec05ff2dd 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.models.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.models.ts @@ -51,6 +51,7 @@ export interface DashboardContext { stateChanged: Observable; stateId: Observable; runChangeDetection: () => void; + dashboardCssClass?: string; } export interface IDashboardController { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarm/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarm/alarms-table-widget.component.ts index bf715330a7..357eed49d2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarm/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarm/alarms-table-widget.component.ts @@ -968,7 +968,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, (AlarmDetailsDialogComponent, { disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + panelClass: ['tb-dialog', 'tb-fullscreen-dialog', this.ctx.stateController.dashboardCtrl.dashboardCtx.dashboardCssClass, this.ctx.widgetCssClass], data: { alarmId: alarm.id.id, allowAcknowledgment: this.allowAcknowledgment, diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts index 51ed592e7e..6edac690e7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts @@ -151,8 +151,8 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O this.widget.widgetContext.containerChangeDetector = this.cd; const cssString = this.widget.widget.config.widgetCss; if (isNotEmptyStr(cssString)) { - this.cssClass = - this.utils.applyCssToElement(this.renderer, this.gridsterItem.el, 'tb-widget-css', cssString); + this.cssClass = this.utils.applyCssToElement(this.renderer, this.gridsterItem.el, 'tb-widget-css', cssString, true); + this.widget.widgetContext.widgetCssClass = this.cssClass; } $(this.gridsterItem.el).on('mousedown', (e) => this.onMouseDown(e.originalEvent)); $(this.gridsterItem.el).on('click', (e) => this.onClicked(e.originalEvent)); diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 293381194e..d3349b2ed4 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -308,6 +308,8 @@ export class WidgetContext { widgetNamespace?: string; subscriptionApi?: WidgetSubscriptionApi; + widgetCssClass?: string; + actionsApi?: WidgetActionsApi; activeEntityInfo?: SubscriptionEntityInfo; From 960481752157fe641b7328952ba5563db38c3df2 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 23 Dec 2025 19:25:14 +0200 Subject: [PATCH 17/62] UI: Add cf details page --- .../calculated-field.component.html | 192 ++++++++++++++++++ .../calculated-field.component.scss | 18 ++ .../calculated-field.component.ts | 166 +++++++++++++++ .../calculated-field.module.ts | 4 +- .../calculated-fields-table-config.ts | 52 +++-- .../calculated-fields-table.component.ts | 4 +- .../debug/entity-debug-settings.service.ts | 4 +- .../calculated-fields-routing.module.ts | 84 +++++++- .../app/shared/models/entity-type.models.ts | 8 +- .../assets/locale/locale.constant-en_US.json | 3 +- 10 files changed, 510 insertions(+), 25 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html create mode 100644 ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html new file mode 100644 index 0000000000..5513770627 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html @@ -0,0 +1,192 @@ + +
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
{{ 'common.general' | translate }}
+
+ + {{ 'entity-field.title' | translate }} + + @if (entityForm.get('name').errors && entityForm.get('name').touched) { + + @if (entityForm.get('name').hasError('required')) { + {{ 'common.hint.title-required' | translate }} + } @else if (entityForm.get('name').hasError('pattern')) { + {{ 'common.hint.title-pattern' | translate }} + } @else if (entityForm.get('name').hasError('maxlength')) { + {{ 'common.hint.title-max-length' | translate }} + } + + } + + + +
+ + + {{ 'common.type' | translate }} + + @for (type of fieldTypes; track type) { + {{ CalculatedFieldTypeTranslations.get(type).name | translate}} + } + + @if (CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint) { + {{ CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint | translate }} + } + +
+ @switch (entityForm.get('type').value) { + @case (CalculatedFieldType.GEOFENCING) { + + } + @case (CalculatedFieldType.PROPAGATION) { + + } + @case (CalculatedFieldType.RELATED_ENTITIES_AGGREGATION) { + + } + @case (CalculatedFieldType.ENTITY_AGGREGATION) { + + } + @default { + + } + } +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss new file mode 100644 index 0000000000..7cfc6dfef2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss @@ -0,0 +1,18 @@ +/** + * 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. + */ +:host { + +} diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts new file mode 100644 index 0000000000..c521c624d4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts @@ -0,0 +1,166 @@ +/// +/// 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 { ChangeDetectorRef, Component, DestroyRef, Inject, Input } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityComponent } from '../../components/entity/entity.component'; +import { FormBuilder, FormGroup, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; +import { AssetInfo } from '@app/shared/models/asset.models'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { + CalculatedFieldConfiguration, + CalculatedFieldInfo, calculatedFieldsEntityTypeList, + CalculatedFieldType, calculatedFieldTypes, CalculatedFieldTypeTranslations, OutputStrategyType +} from '@shared/models/calculated-field.models'; +import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; +import { isDefined } from '@core/utils'; +import { EntityId } from '@shared/models/id/entity-id'; +import { pairwise, switchMap } from 'rxjs/operators'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { BaseData } from '@shared/models/base-data'; +import { Observable, of } from 'rxjs'; +import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { + CalculatedFieldsTableConfig, + CalculatedFieldsTableEntity +} from '@home/components/calculated-fields/calculated-fields-table-config'; + +@Component({ + selector: 'tb-calculated-field', + templateUrl: './calculated-field.component.html', + styleUrls: ['./calculated-field.component.scss'] +}) +export class CalculatedFieldComponent extends EntityComponent { + + @Input() + standalone = false; + + @Input() + entityName: string; + + disabledConfiguration = false; + + readonly tenantId = getCurrentAuthUser(this.store).tenantId; + readonly ownerId = getCurrentAuthUser(this.store).tenantId; + readonly EntityType = EntityType; + readonly calculatedFieldsEntityTypeList = calculatedFieldsEntityTypeList; + readonly CalculatedFieldType = CalculatedFieldType; + readonly fieldTypes = calculatedFieldTypes; + readonly CalculatedFieldTypeTranslations = CalculatedFieldTypeTranslations; + + constructor(protected store: Store, + protected translate: TranslateService, + @Inject('entity') protected entityValue: CalculatedFieldInfo, + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig, + protected fb: FormBuilder, + protected cd: ChangeDetectorRef, + private destroyRef: DestroyRef, + private calculatedFieldsService: CalculatedFieldsService) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + isAssignedToCustomer(entity: AssetInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; + } + + get entityId(): EntityId { + return this.entityForm.get('entityId').value; + } + + get entitiesTableConfig(): CalculatedFieldsTableConfig { + return this.entitiesTableConfigValue; + } + + changeEntity(entity: BaseData): void { + this.entityName = entity?.name; + } + + buildForm(entity?: CalculatedFieldInfo): FormGroup { + const form = this.fb.group({ + name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]], + entityId: [null], + type: [CalculatedFieldType.SIMPLE], + debugSettings: [], + configuration: this.fb.control({} as CalculatedFieldConfiguration), + }); + form.get('type').valueChanges.pipe( + pairwise(), + takeUntilDestroyed(this.destroyRef) + ).subscribe(([prevType, nextType]) => { + if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || + ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { + form.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false}); + } + }); + return form; + } + + updateForm(entity: CalculatedFieldInfo) { + const { configuration = {} as CalculatedFieldConfiguration, type = CalculatedFieldType.SIMPLE, debugSettings = { failuresEnabled: true, allEnabled: true }, entityId = this.entityId, ...value } = entity ?? {}; + if (configuration.type !== CalculatedFieldType.ALARM) { + if (isDefined(configuration?.output) && !configuration?.output?.strategy) { + configuration.output.strategy = {type: OutputStrategyType.RULE_CHAIN}; + } + } + this.entityForm.patchValue({ configuration, type, debugSettings, entityId, ...value }, {emitEvent: false}); + setTimeout(() => this.entityForm.get('type').updateValueAndValidity({onlySelf: true})); + if (!entityId) { + this.entityForm.get('configuration').disable({emitEvent: false}); + this.disabledConfiguration = true; + } + } + + onTestScript(expression?: string): Observable { + const calculatedFieldId = this.entityId?.id; + if (calculatedFieldId) { + return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) + .pipe( + switchMap(event => { + const args = event?.arguments ? JSON.parse(event.arguments) : null; + return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), args, false, expression); + }), + takeUntilDestroyed(this.destroyRef) + ) + } + + return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), null, false, expression); + } + + updateFormState() { + if (this.entityForm) { + if (this.isEditValue) { + this.entityForm.enable({emitEvent: false}); + this.entityForm.get('entityId').disable({emitEvent: false}); + } else { + this.entityForm.disable({emitEvent: false}); + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts index e3f5ac9cc2..8a9241970d 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts @@ -50,6 +50,7 @@ import { import { CalculatedFieldsFilterConfigComponent } from '@home/components/calculated-fields/table-header/calculated-fields-filter-config.component'; +import { CalculatedFieldComponent } from '@home/components/calculated-fields/calculated-field.component'; @NgModule({ declarations: [ @@ -57,7 +58,8 @@ import { CalculatedFieldScriptTestDialogComponent, CalculatedFieldTestArgumentsComponent, CalculatedFieldsHeaderComponent, - CalculatedFieldsFilterConfigComponent + CalculatedFieldsFilterConfigComponent, + CalculatedFieldComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts index 8385b5cb45..977e03e454 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts @@ -20,7 +20,7 @@ import { EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; import { TranslateService } from '@ngx-translate/core'; import { Direction } from '@shared/models/page/sort-order'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; @@ -67,8 +67,11 @@ import { EventsDialogComponent, EventsDialogData } from '@home/dialogs/events-di import { CalculatedFieldsHeaderComponent } from '@home/components/calculated-fields/table-header/calculated-fields-header.component'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { CalculatedFieldComponent } from '@home/components/calculated-fields/calculated-field.component'; +import { Router } from '@angular/router'; -type CalculatedFieldsTableEntity = CalculatedField | CalculatedFieldInfo; +export type CalculatedFieldsTableEntity = CalculatedField | CalculatedFieldInfo; export class CalculatedFieldsTableConfig extends EntityTableConfig { @@ -93,30 +96,33 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { - this.editCalculatedField($event, entity); - this.rowPointer = true; - return true; - }; + this.entityComponent = CalculatedFieldComponent; + this.rowPointer = true; } this.tableTitle = this.pageMode ? '' : this.translate.instant('entity.type-calculated-fields'); - this.detailsPanelEnabled = false; + this.detailsPanelEnabled = this.pageMode; this.entityType = EntityType.CALCULATED_FIELD; this.entityTranslations = entityTypeTranslations.get(EntityType.CALCULATED_FIELD); + this.entityResources = entityTypeResources.get(EntityType.CALCULATED_FIELD); this.entitiesFetchFunction = (pageLink: PageLink) => this.fetchCalculatedFields(pageLink); this.addEntity = this.getCalculatedFieldDialog.bind(this); + this.loadEntity = id => this.calculatedFieldsService.getCalculatedFieldById(id.id); this.deleteEntityTitle = (field) => this.translate.instant('calculated-fields.delete-title', {title: field.name}); this.deleteEntityContent = () => this.translate.instant('calculated-fields.delete-text'); this.deleteEntitiesTitle = count => this.translate.instant('calculated-fields.delete-multiple-title', {count}); this.deleteEntitiesContent = () => this.translate.instant('calculated-fields.delete-multiple-text'); this.deleteEntity = id => this.calculatedFieldsService.deleteCalculatedField(id.id); + + this.onEntityAction = action => this.onCFAction(action); + this.addActionDescriptors = [ { name: this.translate.instant('calculated-fields.create'), @@ -172,14 +178,16 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig true, iconFunction: ({ debugSettings }) => this.entityDebugSettingsService.isDebugActive(debugSettings?.allEnabledUntil) || debugSettings?.failuresEnabled ? 'mdi:bug' : 'mdi:bug-outline', onAction: ($event, entity) => this.onOpenDebugConfig($event, entity), - }, - { + } + ); + if (!this.pageMode) { + this.cellActionDescriptors.push({ name: this.translate.instant('action.edit'), icon: 'edit', isEnabled: () => true, onAction: ($event, entity) => this.editCalculatedField($event, entity), - } - ); + }) + } } fetchCalculatedFields(pageLink: PageLink): Observable> { @@ -354,7 +362,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig this.updateData()); } - private getTestScriptDialog(calculatedField: CalculatedFieldsTableEntity, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit = true, expression?: string): Observable { + getTestScriptDialog(calculatedField: CalculatedFieldsTableEntity, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit = true, expression?: string): Observable { if ( calculatedField.type === CalculatedFieldType.SCRIPT || calculatedField.type === CalculatedFieldType.RELATED_ENTITIES_AGGREGATION || @@ -394,4 +402,22 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig): boolean { + switch (action.action) { + case 'open': + this.openCalculatedField(action.event, action.entity); + return true; + case 'export': + this.exportCalculatedField(action.event, action.entity); + return true; + } + return false; + } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts index 2a0c29e31a..7687277113 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts @@ -36,7 +36,7 @@ import { ImportExportService } from '@shared/import-export/import-export.service import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service'; import { DatePipe } from '@angular/common'; import { UtilsService } from "@core/services/utils.service"; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'tb-calculated-fields-table', @@ -70,6 +70,7 @@ export class CalculatedFieldsTableComponent { private utilsService: UtilsService, private destroyRef: DestroyRef, private route: ActivatedRoute, + private router: Router ) { this.pageMode = !!this.route.snapshot.data.isPage; effect(() => { @@ -88,6 +89,7 @@ export class CalculatedFieldsTableComponent { this.importExportService, this.entityDebugSettingsService, this.utilsService, + this.router, this.pageMode, ); this.cd.markForCheck(); diff --git a/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts b/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts index cb982b9650..76def20a24 100644 --- a/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts +++ b/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts @@ -22,7 +22,9 @@ import { TranslateService } from '@ngx-translate/core'; import { DurationLeftPipe } from '@shared/pipe/duration-left.pipe'; import { EntityDebugSettingPanelConfig } from '@home/components/entity/debug/entity-debug-settings.model'; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class EntityDebugSettingsService { constructor( diff --git a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts index 0ed435075c..03be3d86f6 100644 --- a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts @@ -14,24 +14,94 @@ /// limitations under the License. /// -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { DestroyRef, inject, NgModule, Renderer2 } from '@angular/core'; +import { ActivatedRouteSnapshot, ResolveFn, Router, RouterModule, RouterStateSnapshot, Routes } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { MenuId } from '@core/services/menu.models'; import { CalculatedFieldsTableComponent } from '@home/components/calculated-fields/calculated-fields-table.component'; +import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component'; +import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; +import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; +import { BreadCrumbConfig } from '@shared/components/breadcrumb'; +import { CalculatedFieldsTableConfig } from '@home/components/calculated-fields/calculated-fields-table-config'; +import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DatePipe } from '@angular/common'; +import { ImportExportService } from '@shared/import-export/import-export.service'; +import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service'; +import { UtilsService } from '@core/services/utils.service'; + +export const CalculatedFieldsTableConfigResolver: ResolveFn = + (_route: ActivatedRouteSnapshot, + _state: RouterStateSnapshot, + calculatedFieldsService = inject(CalculatedFieldsService), + translate = inject(TranslateService), + dialog = inject(MatDialog), + store = inject(Store), + datePipe = inject(DatePipe), + destroyRef = inject(DestroyRef), + importExportService = inject(ImportExportService), + entityDebugSettingsService = inject(EntityDebugSettingsService), + utilsService = inject(UtilsService), + router = inject(Router), + ) => { + return new CalculatedFieldsTableConfig( + calculatedFieldsService, + translate, + dialog, + datePipe, + null, + store, + destroyRef, + null, + null, + null, + importExportService, + entityDebugSettingsService, + utilsService, + router, + false, + ); + }; const routes: Routes = [ { path: 'calculatedFields', - component: CalculatedFieldsTableComponent, data: { - auth: [Authority.TENANT_ADMIN], - title: 'entity.type-calculated-fields', breadcrumb: { menuId: MenuId.calculated_fields + } + }, + children: [ + { + path: '', + component: CalculatedFieldsTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'entity.type-calculated-fields', + isPage: true, + } }, - isPage: true, - } + { + path: ':entityId', + component: EntityDetailsPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: entityDetailsPageBreadcrumbLabelFunction, + icon: 'mdi:function-variant' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN], + title: 'entity.type-calculated-fields', + }, + resolve: { + entitiesTableConfig: CalculatedFieldsTableConfigResolver + } + } + ] } ]; diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 48428932e4..bc0d3fd470 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -490,7 +490,7 @@ export const entityTypeTranslations = new Map Date: Wed, 24 Dec 2025 11:53:15 +0200 Subject: [PATCH 18/62] Exclude TbAiNode for Edge-4.1 --- .../server/service/edge/EdgeMsgConstructorUtils.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java index e1c7f1198c..c5579098a4 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java @@ -27,6 +27,7 @@ import com.google.gson.reflect.TypeToken; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.action.TbSaveToCustomCassandraTableNode; +import org.thingsboard.rule.engine.ai.TbAiNode; import org.thingsboard.rule.engine.aws.lambda.TbAwsLambdaNode; import org.thingsboard.rule.engine.rest.TbSendRestApiCallReplyNode; import org.thingsboard.rule.engine.telemetry.TbCalculatedFieldsNode; @@ -167,6 +168,10 @@ public class EdgeMsgConstructorUtils { ); public static final Map> EXCLUDED_NODES_BY_EDGE_VERSION = Map.of( + EdgeVersion.V_4_1_0, + Set.of( + TbAiNode.class.getName() + ), EdgeVersion.V_3_9_0, Set.of( TbCalculatedFieldsNode.class.getName() From bd95161fa7cee882c2b43c53a6ea45dd176045ee Mon Sep 17 00:00:00 2001 From: Nikita Mazurenko Date: Wed, 24 Dec 2025 15:02:57 +0200 Subject: [PATCH 19/62] Exclude TbAiNode for Edge-4.0 --- .../server/service/edge/EdgeMsgConstructorUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java index c5579098a4..513163d54a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java @@ -172,6 +172,10 @@ public class EdgeMsgConstructorUtils { Set.of( TbAiNode.class.getName() ), + EdgeVersion.V_4_0_0, + Set.of( + TbAiNode.class.getName() + ), EdgeVersion.V_3_9_0, Set.of( TbCalculatedFieldsNode.class.getName() From ac7c710e5df60c18032615a97147364ba7038e87 Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Wed, 24 Dec 2025 16:03:02 +0200 Subject: [PATCH 20/62] UI: Fixed bug with cf page --- .../calculated-field.component.html | 7 ++++++- .../calculated-fields/calculated-field.component.ts | 2 +- .../calculated-fields-table-config.ts | 2 +- .../output/calculated-field-output.component.ts | 2 +- .../simple-configuration.component.ts | 2 +- .../components/entity/entity-select.component.html | 2 ++ .../components/entity/entity-select.component.ts | 13 ++++++++++++- 7 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html index 5513770627..10ab150b69 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html @@ -125,7 +125,12 @@ [additionalActionConfig]="entitiesTableConfig.additionalDebugActionConfig" />
- + + {{ 'common.type' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts index c521c624d4..0b62d9bb96 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts @@ -60,7 +60,7 @@ export class CalculatedFieldComponent extends EntityComponent void): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts index e6ff1d1bad..2771795635 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts @@ -109,6 +109,7 @@ export class SimpleConfigurationComponent implements ControlValueAccessor, Valid private propagateChange: (config: SimpeConfiguration) => void = () => { }; constructor(private fb: FormBuilder) { + this.updatedFormWithScript(); this.simpleConfiguration.get('output').valueChanges.pipe( takeUntilDestroyed(), ).subscribe(() => { @@ -151,7 +152,6 @@ export class SimpleConfigurationComponent implements ControlValueAccessor, Valid formValue.expressionSIMPLE = formValue.expression; } this.simpleConfiguration.patchValue(formValue, {emitEvent: false}); - this.updatedFormWithScript(); setTimeout(() => { this.simpleConfiguration.get('arguments').updateValueAndValidity({onlySelf: true}); }); diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-select.component.html index c0b4f1aafb..49e853e722 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.html @@ -25,6 +25,7 @@ [useAliasEntityTypes]="useAliasEntityTypes" [allowedEntityTypes]="allowedEntityTypes" [additionEntityTypes]="additionEntityTypes" + [filterAllowedEntityTypes]="filterAllowedEntityTypes" formControlName="entityType"> diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 5e75426952..fbfb7ae453 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { AfterViewInit, Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { AfterViewInit, Component, DestroyRef, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -26,6 +26,7 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; import { coerceBoolean } from '@shared/decorators/coercion'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatFormFieldAppearance } from '@angular/material/form-field'; +import { BaseData } from '@shared/models/base-data'; @Component({ selector: 'tb-entity-select', @@ -66,6 +67,12 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte @coerceBoolean() useEntityDisplayName = false; + @Input() + filterAllowedEntityTypes: boolean; + + @Output() + entityChanged = new EventEmitter>(); + displayEntityTypeSelect: boolean; AliasEntityType = AliasEntityType; @@ -176,4 +183,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte } } } + + changeEntity(entity: BaseData): void { + this.entityChanged.emit(entity); + } } From a35bbcc1de6d2ddfb845ca6c8295aa7496ec15df Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Wed, 24 Dec 2025 16:31:11 +0200 Subject: [PATCH 21/62] handle future telemetry and fill intervals after restart based on watermark --- ...tractCalculatedFieldProcessingService.java | 6 +- .../EntityAggregationArgumentEntry.java | 74 ++++++++++++++++--- ...EntityAggregationCalculatedFieldState.java | 18 +++++ .../utils/CalculatedFieldArgumentUtils.java | 4 +- .../EntityAggregationCalculatedFieldTest.java | 53 ++++++++++++- 5 files changed, 135 insertions(+), 20 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java b/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java index ec645085e6..cd88c93a71 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java @@ -233,7 +233,7 @@ public abstract class AbstractCalculatedFieldProcessingService { return config.getArguments().entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, - entry -> fetchTimeSeries(ctx.getTenantId(), entityId, entry.getValue(), config.getInterval(), ts) + entry -> fetchTimeSeries(ctx, entityId, entry.getValue(), config.getInterval(), ts) )); } @@ -341,11 +341,11 @@ public abstract class AbstractCalculatedFieldProcessingService { return resolveArgumentValue(argKey, argumentEntryFut); } - private ListenableFuture fetchTimeSeries(TenantId tenantId, EntityId entityId, Argument argument, AggInterval interval, long queryEndTs) { + private ListenableFuture fetchTimeSeries(CalculatedFieldCtx ctx, EntityId entityId, Argument argument, AggInterval interval, long queryEndTs) { long intervalStartTs = interval.getCurrentIntervalStartTs(); long intervalEndTs = interval.getCurrentIntervalEndTs(); ReadTsKvQuery query = new BaseReadTsKvQuery(argument.getRefEntityKey().getKey(), intervalStartTs, queryEndTs, 0, 1, Aggregation.NONE); - return fetchTimeSeriesInternal(tenantId, entityId, query, timeSeries -> transformAggregationArgument(timeSeries, intervalStartTs, intervalEndTs)); + return fetchTimeSeriesInternal(ctx.getTenantId(), entityId, query, timeSeries -> transformAggregationArgument(timeSeries, intervalStartTs, intervalEndTs, ctx)); } private ListenableFuture fetchTsRolling(TenantId tenantId, EntityId entityId, Argument argument, long queryEndTs) { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java index 7ec5098bc3..ef34b6ae8e 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java @@ -19,11 +19,18 @@ import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.script.api.tbel.TbelCfArg; +import org.thingsboard.server.common.data.cf.configuration.aggregation.single.EntityAggregationCalculatedFieldConfiguration; +import org.thingsboard.server.common.data.cf.configuration.aggregation.single.interval.AggInterval; +import org.thingsboard.server.common.data.cf.configuration.aggregation.single.interval.Watermark; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntryType; +import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry; +import java.time.Instant; +import java.time.ZonedDateTime; import java.util.Map; +import java.util.concurrent.TimeUnit; @Data public class EntityAggregationArgumentEntry implements ArgumentEntry { @@ -32,10 +39,18 @@ public class EntityAggregationArgumentEntry implements ArgumentEntry { private boolean forceResetPrevious; + private AggInterval interval; + private long watermarkDuration; + public EntityAggregationArgumentEntry(Map aggIntervals) { this.aggIntervals = aggIntervals; } + public EntityAggregationArgumentEntry(Map aggIntervals, CalculatedFieldCtx ctx) { + this(aggIntervals); + setCtx(ctx); + } + @Override public ArgumentEntryType getType() { return ArgumentEntryType.ENTITY_AGGREGATION; @@ -46,29 +61,64 @@ public class EntityAggregationArgumentEntry implements ArgumentEntry { return aggIntervals; } + public void setCtx(CalculatedFieldCtx ctx) { + var configuration = (EntityAggregationCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration(); + interval = configuration.getInterval(); + Watermark watermark = configuration.getWatermark(); + watermarkDuration = watermark == null ? 0 : TimeUnit.SECONDS.toMillis(watermark.getDuration()); + } + @Override public boolean updateEntry(ArgumentEntry entry) { - boolean updated = false; if (entry instanceof EntityAggregationArgumentEntry entityAggEntry) { aggIntervals.putAll(entityAggEntry.getAggIntervals()); + return true; } else if (entry instanceof SingleValueArgumentEntry singleValueArgEntry) { long entryTs = singleValueArgEntry.getTs(); - long argUpdateTs = System.currentTimeMillis(); - for (Map.Entry aggIntervalEntry : aggIntervals.entrySet()) { - if (singleValueArgEntry.isForceResetPrevious()) { - aggIntervalEntry.getValue().setLastArgsRefreshTs(argUpdateTs); - updated = true; - continue; - } - if (aggIntervalEntry.getKey().belongsToInterval(entryTs)) { - aggIntervalEntry.getValue().setLastArgsRefreshTs(argUpdateTs); - return true; - } + long now = System.currentTimeMillis(); + if (updateExistingIntervals(singleValueArgEntry, entryTs, now)) { + return true; + } + return createNewInterval(entryTs, now); + } + return false; + } + + private boolean updateExistingIntervals(SingleValueArgumentEntry entry, long entryTs, long now) { + boolean updated = false; + + for (Map.Entry aggIntervalEntry : aggIntervals.entrySet()) { + AggIntervalEntry interval = aggIntervalEntry.getKey(); + AggIntervalEntryStatus status = aggIntervalEntry.getValue(); + if (entry.isForceResetPrevious()) { + status.setLastArgsRefreshTs(now); + updated = true; + continue; + } + if (interval.belongsToInterval(entryTs)) { + status.setLastArgsRefreshTs(now); + return true; } } + return updated; } + private boolean createNewInterval(long entryTs, long now) { + ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(entryTs), interval.getZoneId()); + + long startTs = interval.getDateTimeIntervalStartTs(zdt); + long endTs = interval.getDateTimeIntervalEndTs(zdt); + + if (now - endTs > watermarkDuration) { + return false; + } + + AggIntervalEntry newInterval = new AggIntervalEntry(startTs, endTs); + aggIntervals.computeIfAbsent(newInterval, i -> new AggIntervalEntryStatus(now)); + return true; + } + @Override public boolean isEmpty() { return aggIntervals.isEmpty(); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java index 0f6e01a344..59335717fb 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java @@ -82,6 +82,15 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt interval = configuration.getInterval(); metrics = configuration.getMetrics(); produceIntermediateResult = configuration.isProduceIntermediateResult(); + setCtxToArguments(); + } + + private void setCtxToArguments() { + arguments.values().forEach(argument -> { + if (argument instanceof EntityAggregationArgumentEntry entityAggArgument) { + entityAggArgument.setCtx(ctx); + } + }); } @Override @@ -154,8 +163,10 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt } private void fillMissingIntervals() { + long now = System.currentTimeMillis(); ZoneId zoneId = interval.getZoneId(); long currentIntervalEndTs = interval.getCurrentIntervalEndTs(); + long watermarkThresholdTs = now - watermarkDuration; Map> intervals = getIntervals(); AggIntervalEntry lastIntervalEntry = intervals.keySet().stream().max(Comparator.comparing(AggIntervalEntry::getEndTs)).orElse(null); @@ -169,6 +180,13 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt while (nextEnd.toInstant().toEpochMilli() <= currentIntervalEndTs) { long nextStartTs = nextStart.toInstant().toEpochMilli(); long nextEndTs = nextEnd.toInstant().toEpochMilli(); + + if (nextEndTs < watermarkThresholdTs) { + nextStart = nextEnd; + nextEnd = interval.getNextIntervalStart(nextStart); + continue; + } + AggIntervalEntry missing = new AggIntervalEntry(nextStartTs, nextEndTs); arguments.forEach((argName, argumentEntry) -> { diff --git a/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java b/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java index 7e0701cd2d..d6f08cbc69 100644 --- a/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java @@ -75,7 +75,7 @@ public class CalculatedFieldArgumentUtils { return new SingleValueArgumentEntry(); } - public static ArgumentEntry transformAggregationArgument(List timeSeries, long startIntervalTs, long endIntervalTs) { + public static ArgumentEntry transformAggregationArgument(List timeSeries, long startIntervalTs, long endIntervalTs, CalculatedFieldCtx ctx) { Map aggIntervals = new HashMap<>(); AggIntervalEntry aggIntervalEntry = new AggIntervalEntry(startIntervalTs, endIntervalTs); if (timeSeries == null || timeSeries.isEmpty()) { @@ -83,7 +83,7 @@ public class CalculatedFieldArgumentUtils { } else { aggIntervals.put(aggIntervalEntry, new AggIntervalEntryStatus(System.currentTimeMillis())); } - return new EntityAggregationArgumentEntry(aggIntervals); + return new EntityAggregationArgumentEntry(aggIntervals, ctx); } private static KvEntry createDefaultKvEntry(Argument argument) { diff --git a/application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java b/application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java index 253d02ebe5..66c11a06a6 100644 --- a/application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java +++ b/application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java @@ -55,6 +55,8 @@ import static org.thingsboard.server.cf.CalculatedFieldIntegrationTest.POLL_INTE @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest { + private final String TZ = "Europe/Kyiv"; + private Tenant savedTenant; @Before @@ -93,7 +95,7 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest public void testCreateCfAndNoTelemetryDuringInterval_checkAggregation() throws Exception { Device device = createDevice("Device", "1234567890111"); - CustomInterval customInterval = new CustomInterval("Europe/Kyiv", 0L, 5L); + CustomInterval customInterval = new CustomInterval(TZ, 0L, 5L); createConsumptionCF(device.getId(), customInterval, null); long interval = customInterval.getCurrentIntervalDurationMillis(); @@ -113,7 +115,7 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest public void testCreateCfWithoutWatermark_checkAggregation() throws Exception { Device device = createDevice("Device", "1234567890111"); - CustomInterval customInterval = new CustomInterval("Europe/Kyiv", 0L, 5L); + CustomInterval customInterval = new CustomInterval(TZ, 0L, 5L); createConsumptionCF(device.getId(), customInterval, null); long currentIntervalStartTs = customInterval.getCurrentIntervalStartTs(); @@ -156,7 +158,7 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest public void testCreateCfWithWatermark_checkAggregationDuringWatermark() throws Exception { Device device = createDevice("Device", "1234567890111"); - CustomInterval customInterval = new CustomInterval("Europe/Kyiv", 0L, 5L); + CustomInterval customInterval = new CustomInterval(TZ, 0L, 5L); Watermark watermark = new Watermark(10); createConsumptionCF(device.getId(), customInterval, watermark); @@ -196,6 +198,51 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest }); } + @Test + public void testSendFutureTelemetry_checkAggregation() throws Exception { + Device device = createDevice("Device", "1234567890111"); + + CustomInterval customInterval = new CustomInterval(TZ, 0L, 2L); + createConsumptionCF(device.getId(), customInterval, null); + + long currentIntervalStartTs = customInterval.getCurrentIntervalStartTs(); + + long tsBeforeInterval = currentIntervalStartTs - 1000; + long tsInInterval_1 = currentIntervalStartTs + 1000; + long tsInInterval_2 = currentIntervalStartTs + 500; + long tsInInterval_3 = currentIntervalStartTs + 200; + postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":120}}", tsBeforeInterval)); + postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":100}}", tsInInterval_1)); + postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":180}}", tsInInterval_2)); + postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":120}}", tsInInterval_3)); + + long interval = customInterval.getCurrentIntervalDurationMillis(); + + await().alias("create CF -> perform aggregation after interval end") + .atMost(2 * interval, TimeUnit.MILLISECONDS) + .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) + .untilAsserted(() -> { + ObjectNode result = getLatestTelemetry(device.getId(), "consumption", "avgConsumption"); + assertThat(result).isNotNull(); + assertThat(result.get("consumption").get(0).get("value").asText()).isEqualTo("400"); + assertThat(result.get("avgConsumption").get(0).get("value").asText()).isEqualTo("133"); + }); + + postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":500}}", currentIntervalStartTs + 4500L)); + + await().alias("update telemetry that belongs to future interval -> check aggregation ") + .atMost(3 * interval, TimeUnit.MILLISECONDS) + .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) + .untilAsserted(() -> { + ObjectNode result = getLatestTelemetry(device.getId(), "consumption", "avgConsumption"); + assertThat(result).isNotNull(); + assertThat(result.get("consumption").get(0).get("value").asText()).isEqualTo("500"); + assertThat(result.get("consumption").get(0).get("ts").asLong()).isEqualTo(currentIntervalStartTs + 4000L); + assertThat(result.get("avgConsumption").get(0).get("value").asText()).isEqualTo("500"); + assertThat(result.get("avgConsumption").get(0).get("ts").asLong()).isEqualTo(currentIntervalStartTs + 4000L); + }); + } + private CalculatedField createConsumptionCF(EntityId entityId, AggInterval aggInterval, Watermark watermark) { Map arguments = new HashMap<>(); Argument argument = new Argument(); From 2ee94a616f2cb3f2658b912e40c9023a271ac8c9 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 24 Dec 2025 16:36:08 +0200 Subject: [PATCH 22/62] UI: Add cf details panel tabs refactor edit value --- .../alarm-rules/alarm-rules-table-config.ts | 2 +- .../calculated-field.component.html | 259 +++++++----------- .../calculated-field.component.scss | 36 ++- .../calculated-field.component.ts | 52 ++-- .../calculated-field.module.ts | 3 +- .../calculated-fields-table-config.ts | 28 +- .../entity-aggregation-component.component.ts | 10 +- .../geofencing-configuration.component.ts | 4 +- .../calculated-field-output.component.ts | 2 +- .../propagation-configuration.component.ts | 6 +- .../simple-configuration.component.ts | 1 - .../entity/entity-tabs.component.ts | 10 +- .../components/event/event-table-config.ts | 16 +- .../components/event/event-table.component.ts | 24 +- .../home/dialogs/events-dialog.component.html | 1 + .../home/dialogs/events-dialog.component.ts | 6 +- ...ule.ts => calculated-field-page.module.ts} | 10 +- .../calculated-fields-routing.module.ts | 4 +- .../calculated-fields-tabs.component.html | 30 ++ .../calculated-fields-tabs.component.ts | 49 ++++ .../modules/home/pages/home-pages.module.ts | 4 +- .../shared/models/calculated-field.models.ts | 6 + .../assets/locale/locale.constant-en_US.json | 1 + 23 files changed, 342 insertions(+), 222 deletions(-) rename ui-ngx/src/app/modules/home/pages/calculated-fields/{calculated-fields.module.ts => calculated-field-page.module.ts} (77%) create mode 100644 ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts index 3d1c066839..b4082fb75c 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts @@ -288,7 +288,7 @@ export class AlarmRulesTableConfig extends EntityTableConfig false + debugActionDisabled: true } }) .afterClosed() diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html index 10ab150b69..105c1c74a0 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html @@ -20,178 +20,125 @@ [disabled]="(isLoading$ | async)" (click)="onEntityAction($event, 'open')" [class.!hidden]="isEdit || isDetailsPage"> - {{'common.open-details-page' | translate }} + {{ 'common.open-details-page' | translate }} - - - - - - - - - - + + + + + + + + + + -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
{{ 'common.general' | translate }}
-
- - {{ 'entity-field.title' | translate }} - - @if (entityForm.get('name').errors && entityForm.get('name').touched) { - - @if (entityForm.get('name').hasError('required')) { - {{ 'common.hint.title-required' | translate }} - } @else if (entityForm.get('name').hasError('pattern')) { - {{ 'common.hint.title-pattern' | translate }} - } @else if (entityForm.get('name').hasError('maxlength')) { - {{ 'common.hint.title-max-length' | translate }} - } - - } - - - -
- - - - {{ 'common.type' | translate }} - - @for (type of fieldTypes; track type) { - {{ CalculatedFieldTypeTranslations.get(type).name | translate}} - } - - @if (CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint) { - {{ CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint | translate }} +
+
+
+
{{ 'common.general' | translate }}
+
+ + {{ 'entity-field.title' | translate }} + + @if (entityForm.get('name').errors && entityForm.get('name').touched) { + + @if (entityForm.get('name').hasError('required')) { + {{ 'common.hint.title-required' | translate }} + } @else if (entityForm.get('name').hasError('pattern')) { + {{ 'common.hint.title-pattern' | translate }} + } @else if (entityForm.get('name').hasError('maxlength')) { + {{ 'common.hint.title-max-length' | translate }} + } + } + +
- @switch (entityForm.get('type').value) { - @case (CalculatedFieldType.GEOFENCING) { - - } - @case (CalculatedFieldType.PROPAGATION) { - - } - @case (CalculatedFieldType.RELATED_ENTITIES_AGGREGATION) { - - } - @case (CalculatedFieldType.ENTITY_AGGREGATION) { - - } - @default { - + + + + {{ 'common.type' | translate }} + + @for (type of fieldTypes; track type) { + {{ CalculatedFieldTypeTranslations.get(type).name | translate }} + } + + @if (CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint) { + {{ CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint | translate }} } - } +
+ @switch (entityForm.get('type').value) { + @case (CalculatedFieldType.GEOFENCING) { + + } + @case (CalculatedFieldType.PROPAGATION) { + + } + @case (CalculatedFieldType.RELATED_ENTITIES_AGGREGATION) { + + } + @case (CalculatedFieldType.ENTITY_AGGREGATION) { + + } + @default { + + } + }
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss index 7cfc6dfef2..5bcd2552fc 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss @@ -13,6 +13,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host { +:host ::ng-deep { + .tbel-script-lang-chip { + line-height: 20px; + font-size: 14px; + font-weight: 500; + color: white; + border-radius: 100px; + width: 70px; + min-width: 70px; + display: flex; + justify-content: center; + margin-top: 2px; + margin-right: 4px; + } + .tb-js-func { + .ace_tb { + &.ace_calculated-field { + &-ctx { + color: #C52F00; + } + &-args { + color: #185F2A; + } + &-key { + color: #c24c1a; + } + &-time-window, &-values, &-func, &-value, &-ts, &-latestTs { + color: #7214D0; + } + &-start-ts, &-end-ts { + color: #2CAA00; + } + } + } + } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts index 0b62d9bb96..308e350116 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts @@ -18,17 +18,17 @@ import { ChangeDetectorRef, Component, DestroyRef, Inject, Input } from '@angula import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; -import { FormBuilder, FormGroup, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; -import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; -import { NULL_UUID } from '@shared/models/id/has-uuid'; -import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { EntityType } from '@shared/models/entity-type.models'; import { TranslateService } from '@ngx-translate/core'; -import { AssetInfo } from '@app/shared/models/asset.models'; -import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { CalculatedFieldConfiguration, - CalculatedFieldInfo, calculatedFieldsEntityTypeList, - CalculatedFieldType, calculatedFieldTypes, CalculatedFieldTypeTranslations, OutputStrategyType + CalculatedFieldInfo, + calculatedFieldsEntityTypeList, + CalculatedFieldType, + calculatedFieldTypes, + CalculatedFieldTypeTranslations, + OutputStrategyType } from '@shared/models/calculated-field.models'; import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; import { isDefined } from '@core/utils'; @@ -36,13 +36,14 @@ import { EntityId } from '@shared/models/id/entity-id'; import { pairwise, switchMap } from 'rxjs/operators'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { BaseData } from '@shared/models/base-data'; -import { Observable, of } from 'rxjs'; +import { Observable } from 'rxjs'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { CalculatedFieldsTableConfig, CalculatedFieldsTableEntity } from '@home/components/calculated-fields/calculated-fields-table-config'; +import { TenantId } from '@shared/models/id/tenant-id'; @Component({ selector: 'tb-calculated-field', @@ -60,7 +61,7 @@ export class CalculatedFieldComponent extends EntityComponent this.entitiesTableConfig.additionalDebugActionConfig.action( + { id: this.entity.id, ...this.entityFormValue() }, false, + (expression) => { + if (expression) { + this.entityForm.get('configuration').setValue({...this.entityFormValue().configuration, expression}); + this.entityForm.get('configuration').markAsDirty(); + } + }), + }; get entityId(): EntityId { return this.entityForm.get('entityId').value; @@ -114,9 +123,11 @@ export class CalculatedFieldComponent extends EntityComponent { - if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || - ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { - form.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false}); + if (this.isEditValue) { + if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || + ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { + form.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false}); + } } }); return form; @@ -129,8 +140,11 @@ export class CalculatedFieldComponent extends EntityComponent this.entityForm.get('type').updateValueAndValidity({onlySelf: true})); + this.entityForm.patchValue({ type }, {emitEvent: false}); + setTimeout(() => { + this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false}); + this.entityForm.get('type').updateValueAndValidity({onlySelf: true}); + }); if (!entityId) { this.entityForm.get('configuration').disable({emitEvent: false}); this.disabledConfiguration = true; @@ -138,7 +152,7 @@ export class CalculatedFieldComponent extends EntityComponent { - const calculatedFieldId = this.entityId?.id; + const calculatedFieldId = this.entity?.id?.id; if (calculatedFieldId) { return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) .pipe( diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts index 8a9241970d..2bcfcd7a1b 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts @@ -59,7 +59,7 @@ import { CalculatedFieldComponent } from '@home/components/calculated-fields/cal CalculatedFieldTestArgumentsComponent, CalculatedFieldsHeaderComponent, CalculatedFieldsFilterConfigComponent, - CalculatedFieldComponent + CalculatedFieldComponent, ], imports: [ CommonModule, @@ -74,6 +74,7 @@ import { CalculatedFieldComponent } from '@home/components/calculated-fields/cal exports: [ CalculatedFieldDialogComponent, CalculatedFieldScriptTestDialogComponent, + CalculatedFieldComponent, ] }) export class CalculatedFieldsModule {} diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts index e56944e00f..5841d9e511 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts @@ -46,6 +46,7 @@ import { CalculatedFieldsQuery, CalculatedFieldType, CalculatedFieldTypeTranslations, + debugCfActionEnabled, getCalculatedFieldArgumentsEditorCompleter, getCalculatedFieldArgumentsHighlights, PropagationWithExpression, @@ -62,7 +63,7 @@ import { EntityDebugSettingsService } from '@home/components/entity/debug/entity import { DatePipe } from '@angular/common'; import { UtilsService } from "@core/services/utils.service"; import { ActionNotificationShow } from "@core/notification/notification.actions"; -import { CalculatedFieldEventBody, DebugEventType, Event as DebugEvent, EventType } from '@shared/models/event.models'; +import { CalculatedFieldEventBody, DebugEventType, EventType } from '@shared/models/event.models'; import { EventsDialogComponent, EventsDialogData } from '@home/dialogs/events-dialog.component'; import { CalculatedFieldsHeaderComponent @@ -70,6 +71,7 @@ import { import { EntityAction } from '@home/models/entity/entity-component.models'; import { CalculatedFieldComponent } from '@home/components/calculated-fields/calculated-field.component'; import { Router } from '@angular/router'; +import { CalculatedFieldsTabsComponent } from '@home/pages/calculated-fields/calculated-fields-tabs.component'; export type CalculatedFieldsTableEntity = CalculatedField | CalculatedFieldInfo; @@ -78,7 +80,9 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig this.openDebugEventsDialog.call(this, null, calculatedField), + action: (calculatedField: CalculatedFieldsTableEntity, + openCalculatedFieldEdit = true, + afterCloseCallback?: (expression: string) => void) => this.openDebugEventsDialog.call(this, null, calculatedField, openCalculatedFieldEdit, afterCloseCallback), }; calculatedFieldFilterConfig: CalculatedFieldsQuery; @@ -104,6 +108,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig this.fetchCalculatedFields(pageLink); this.addEntity = this.getCalculatedFieldDialog.bind(this); + this.saveEntity = (cf) => this.calculatedFieldsService.saveCalculatedField(cf); this.loadEntity = id => this.calculatedFieldsService.getCalculatedFieldById(id.id); this.deleteEntityTitle = (field) => this.translate.instant('calculated-fields.delete-title', {title: field.name}); this.deleteEntityContent = () => this.translate.instant('calculated-fields.delete-text'); @@ -253,17 +259,11 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig void ): void { $event?.stopPropagation(); - const debugActionEnabledFn = (event: DebugEvent) => { - return (calculatedField.type === CalculatedFieldType.SCRIPT || - (calculatedField.type === CalculatedFieldType.PROPAGATION && - calculatedField.configuration.applyExpressionToResolvedArguments) - ) && !!(event as DebugEvent).body.arguments; - }; const onDebugEventSelected = (event: CalculatedFieldEventBody, dialogRef: MatDialogRef) => { - this.getTestScriptDialog(calculatedField, JSON.parse(event.arguments)) + this.getTestScriptDialog(calculatedField, JSON.parse(event.arguments), openCalculatedFieldEdit) .subscribe(expression => dialogRef.close(expression)); }; @@ -278,11 +278,15 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { + if (afterCloseCallback) { + afterCloseCallback(value) + } + }); } private exportCalculatedField($event: Event, calculatedField: CalculatedFieldsTableEntity): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts index e3655f5370..6f281b8e25 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts @@ -182,10 +182,12 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor interval: {...value.interval, allowOffsetSec: isDefinedAndNotNull(value?.interval?.offsetSec)} } this.entityAggregationConfiguration.patchValue(data, {emitEvent: false}); - this.checkAggIntervalType(this.entityAggregationConfiguration.get('interval.type').value); - this.checkIntervalDuration(this.entityAggregationConfiguration.get('interval.allowOffsetSec').value); - this.checkWatermark(this.entityAggregationConfiguration.get('allowWatermark').value); - this.checkProduceIntermediate(); + if (this.entityAggregationConfiguration.enabled) { + this.checkAggIntervalType(this.entityAggregationConfiguration.get('interval.type').value); + this.checkIntervalDuration(this.entityAggregationConfiguration.get('interval.allowOffsetSec').value); + this.checkWatermark(this.entityAggregationConfiguration.get('allowWatermark').value); + this.checkProduceIntermediate(); + } this.updatedOffsetHint(); setTimeout(() => { this.entityAggregationConfiguration.get('arguments').updateValueAndValidity({onlySelf: true}); diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts index 3406922b9e..9df60b6094 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts @@ -124,7 +124,9 @@ export class GeofencingConfigurationComponent implements ControlValueAccessor, V writeValue(config: CalculatedFieldGeofencingConfiguration): void { this.geofencingConfiguration.patchValue(config, {emitEvent: false}); this.checkRelatedEntity(this.geofencingConfiguration.get('zoneGroups').value); - this.checkScheduledUpdateEnabled(this.geofencingConfiguration.get('scheduledUpdateEnabled').value); + if (this.geofencingConfiguration.enabled) { + this.checkScheduledUpdateEnabled(this.geofencingConfiguration.get('scheduledUpdateEnabled').value); + } } registerOnChange(fn: (config: CalculatedFieldGeofencingConfiguration) => void): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts index c254dcd2b4..00fea861f6 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts @@ -148,7 +148,7 @@ export class CalculatedFieldOutputComponent implements ControlValueAccessor, Val for (const propName of Object.keys(changes)) { const change = changes[propName]; if (change.currentValue !== change.previousValue) { - if (propName === 'simpleMode') { + if (propName === 'simpleMode' && !this.disabled) { this.updatedFormWithMode(); if (!change.firstChange) { this.outputForm.updateValueAndValidity(); diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts index 1c493148b3..33acbcf86d 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts @@ -131,9 +131,11 @@ export class PropagationConfigurationComponent implements ControlValueAccessor, writeValue(value: PropagationWithExpression): void { value.expression = value.expression ?? calculatedFieldDefaultScript; this.propagateConfiguration.patchValue(value, {emitEvent: false}); - this.updatedFormWithScript(); + if (!this.disabled) { + this.updatedFormWithScript(); + } setTimeout(() => { - this.propagateConfiguration.get('arguments').updateValueAndValidity({onlySelf: true}); + this.propagateConfiguration.get('arguments').updateValueAndValidity({onlySelf: true, emitEvent: false}); }); } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts index 2771795635..ec07fcae03 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts @@ -109,7 +109,6 @@ export class SimpleConfigurationComponent implements ControlValueAccessor, Valid private propagateChange: (config: SimpeConfiguration) => void = () => { }; constructor(private fb: FormBuilder) { - this.updatedFormWithScript(); this.simpleConfiguration.get('output').valueChanges.pipe( takeUntilDestroyed(), ).subscribe(() => { diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts index 3d297f0317..066ef268e5 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts @@ -16,7 +16,7 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { PageComponent } from '@shared/components/page.component'; -import { AfterViewInit, Directive, Input, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { AfterViewInit, Directive, inject, Input, OnInit, QueryList, ViewChildren } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @@ -100,9 +100,11 @@ export abstract class EntityTabsComponent, entityTabsChanged = this.entityTabsSubject.asObservable(); - protected constructor(protected store: Store) { - super(store); - this.authUser = getCurrentAuthUser(store); + protected store: Store = inject(Store); + + protected constructor(...args: unknown[]) { + super(); + this.authUser = getCurrentAuthUser(this.store); } ngOnInit() { diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index 03fc641735..accc3d7060 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -22,7 +22,14 @@ import { EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { DebugEventType, Event, EventBody, EventType, FilterEventBody } from '@shared/models/event.models'; +import { + DebugEventType, + Event as DebugEvent, + Event, + EventBody, + EventType, + FilterEventBody +} from '@shared/models/event.models'; import { TimePageLink } from '@shared/models/page/page-link'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; @@ -95,7 +102,8 @@ export class EventTableConfig extends EntityTableConfig { private store: Store, public testButtonLabel?: string, private debugEventSelected?: EventEmitter, - public hideClearEventAction = false) { + public hideClearEventAction = false, + public disableDebugEventAction = false) { super(); this.loadDataOnInit = false; this.tableTitle = ''; @@ -453,7 +461,7 @@ export class EventTableConfig extends EntityTableConfig { this.cellActionDescriptors.push({ name: this.translate.instant('rulenode.test-with-this-message', {test: this.translate.instant(this.testButtonLabel)}), icon: 'bug_report', - isEnabled: (entity) => entity.body.type === 'IN' || entity.body.error !== undefined, + isEnabled: (entity) => (entity.body.type === 'IN' || entity.body.error !== undefined) && !this.disableDebugEventAction, onAction: ($event, entity) => { this.debugEventSelected.next(entity.body); } @@ -464,7 +472,7 @@ export class EventTableConfig extends EntityTableConfig { this.cellActionDescriptors.push({ name: this.translate.instant('calculated-fields.test-with-this-message'), icon: 'bug_report', - isEnabled: () => true, + isEnabled: (event) => !this.disableDebugEventAction && !!(event as DebugEvent).body.arguments, onAction: (_, entity) => this.debugEventSelected.next(entity.body) }); break; diff --git a/ui-ngx/src/app/modules/home/components/event/event-table.component.ts b/ui-ngx/src/app/modules/home/components/event/event-table.component.ts index 53cb8e1b96..7011c23449 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table.component.ts @@ -17,10 +17,12 @@ import { AfterViewInit, ChangeDetectorRef, - Component, EventEmitter, + Component, + EventEmitter, Input, OnDestroy, - OnInit, Output, + OnInit, + Output, ViewChild, ViewContainerRef } from '@angular/core'; @@ -61,6 +63,21 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy { @Input() hideClearEventAction: boolean = false; + private disableDebugEventActionValue = false; + + get disableDebugEventAction() { + return this.disableDebugEventActionValue; + } + + @Input() + set disableDebugEventAction(value) { + this.disableDebugEventActionValue = value; + if (this.eventTableConfig) { + this.eventTableConfig.disableDebugEventAction = this.disableDebugEventAction; + this.eventTableConfig.updateCellAction(); + } + }; + activeValue = false; dirtyValue = false; entityIdValue: EntityId; @@ -151,7 +168,8 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy { this.store, this.functionTestButtonLabel, this.debugEventSelected, - this.hideClearEventAction + this.hideClearEventAction, + this.disableDebugEventAction ); } diff --git a/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.html b/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.html index 655c25545d..ab48435b2d 100644 --- a/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.html +++ b/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.html @@ -34,6 +34,7 @@ [tenantId]="data.tenantId" [entityId]="data.entityId" [functionTestButtonLabel]="data.functionTestButtonLabel ?? ''" + [disableDebugEventAction]="data.debugActionDisabled" (debugEventSelected)="this.onDebugEventSelected($event)" />
diff --git a/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.ts index d5339de3b2..8008e74523 100644 --- a/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.ts @@ -23,7 +23,6 @@ import { AppState } from '@core/core.state'; import { Router } from '@angular/router'; import { DebugEventType, EventType } from '@shared/models/event.models'; import { EntityId } from '@shared/models/id/entity-id'; -import { BaseData, HasId } from '@shared/models/base-data'; export interface EventsDialogData { title: string; @@ -34,7 +33,7 @@ export interface EventsDialogData { disabledEventTypes?: Array; functionTestButtonLabel?: string; onDebugEventSelected?: (event: any, dialogRef: MatDialogRef) => void; - debugActionEnabledFn?: (event: BaseData) => boolean; + debugActionDisabled?: boolean; } @Component({ @@ -54,9 +53,6 @@ export class EventsDialogComponent extends DialogComponent 0) { - this.eventsTable.entitiesTable.cellActionDescriptors[0].isEnabled = this.data.debugActionEnabledFn; - } this.eventsTable.entitiesTable.updateData(); } diff --git a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields.module.ts b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-field-page.module.ts similarity index 77% rename from ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields.module.ts rename to ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-field-page.module.ts index 01ed3577e8..f0672c2e5e 100644 --- a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields.module.ts +++ b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-field-page.module.ts @@ -20,15 +20,19 @@ import { SharedModule } from '@shared/shared.module'; import { HomeDialogsModule } from '../../dialogs/home-dialogs.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { CalculatedFieldsRoutingModule } from '@home/pages/calculated-fields/calculated-fields-routing.module'; +import { CalculatedFieldsModule } from '@home/components/calculated-fields/calculated-field.module'; +import { CalculatedFieldsTabsComponent } from '@home/pages/calculated-fields/calculated-fields-tabs.component'; @NgModule({ - declarations: [], + declarations: [ + CalculatedFieldsTabsComponent + ], imports: [ CommonModule, SharedModule, HomeComponentsModule, HomeDialogsModule, - CalculatedFieldsRoutingModule + CalculatedFieldsRoutingModule, ] }) -export class CalculatedFieldsModule { } \ No newline at end of file +export class CalculatedFieldPageModule { } \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts index 03be3d86f6..e703445155 100644 --- a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { DestroyRef, inject, NgModule, Renderer2 } from '@angular/core'; +import { DestroyRef, inject, NgModule } from '@angular/core'; import { ActivatedRouteSnapshot, ResolveFn, Router, RouterModule, RouterStateSnapshot, Routes } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { MenuId } from '@core/services/menu.models'; @@ -63,7 +63,7 @@ export const CalculatedFieldsTableConfigResolver: ResolveFn +@if (entity) { + + + +} diff --git a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts new file mode 100644 index 0000000000..0c41ddc33d --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts @@ -0,0 +1,49 @@ +/// +/// Copyright © 2016-2025 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; +import { CalculatedFieldEventBody, DebugEventType, EventType } from '@shared/models/event.models'; +import type { + CalculatedFieldsTableConfig, + CalculatedFieldsTableEntity +} from '@home/components/calculated-fields/calculated-fields-table-config'; +import { debugCfActionEnabled } from '@shared/models/calculated-field.models'; + +@Component({ + selector: 'tb-calculated-fields-tabs', + templateUrl: './calculated-fields-tabs.component.html', + styleUrls: [] +}) +export class CalculatedFieldsTabsComponent extends EntityTabsComponent { + + readonly DebugEventType = DebugEventType; + readonly EventType = EventType; + + constructor() { + super(); + } + + get debugActionDisabled(): boolean { + return !debugCfActionEnabled(this.entity); + }; + + onDebugEventSelected(event: CalculatedFieldEventBody) { + (this.entitiesTableConfig as CalculatedFieldsTableConfig).getTestScriptDialog(this.entity, JSON.parse(event.arguments)) + .subscribe((expression) => { + }); + }; +} diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index c43b89d141..d84b646525 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -27,7 +27,7 @@ import { UserModule } from '@modules/home/pages/user/user.module'; import { DeviceModule } from '@modules/home/pages/device/device.module'; import { AssetModule } from '@modules/home/pages/asset/asset.module'; import { EntityViewModule } from '@modules/home/pages/entity-view/entity-view.module'; -import { CalculatedFieldsModule } from '@home/pages/calculated-fields/calculated-fields.module'; +import { CalculatedFieldPageModule } from '@home/pages/calculated-fields/calculated-field-page.module'; import { RuleChainModule } from '@modules/home/pages/rulechain/rulechain.module'; import { WidgetLibraryModule } from '@modules/home/pages/widget/widget-library.module'; import { DashboardModule } from '@modules/home/pages/dashboard/dashboard.module'; @@ -70,7 +70,7 @@ import { AiModelModule } from '@home/pages/ai-model/ai-model.module'; EdgeModule, EntityViewModule, CustomerModule, - CalculatedFieldsModule, + CalculatedFieldPageModule, RuleChainModule, WidgetLibraryModule, DashboardModule, diff --git a/ui-ngx/src/app/shared/models/calculated-field.models.ts b/ui-ngx/src/app/shared/models/calculated-field.models.ts index 825c08a69f..30f5a27686 100644 --- a/ui-ngx/src/app/shared/models/calculated-field.models.ts +++ b/ui-ngx/src/app/shared/models/calculated-field.models.ts @@ -540,6 +540,12 @@ export const getCalculatedFieldCurrentEntityFilter = (entityName: string, entity } } +export const debugCfActionEnabled = (cf: CalculatedField) => { + return (cf.type === CalculatedFieldType.SCRIPT || + (cf.type === CalculatedFieldType.PROPAGATION && cf.configuration.applyExpressionToResolvedArguments) + ); +} + export interface CalculatedFieldArgumentValueBase { argumentName: string; type: ArgumentType; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 73a8bbf23f..8c2ecc2890 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1221,6 +1221,7 @@ "calculated-field-filter-title": "Calculated field filter", "filter-title": "Filter", "calculated-field-types": "Calculated field types", + "events": "Events", "any-type": "Any type", "metrics": { "metrics": "Metrics", From c50b24d014bead4905627a7e2e6269020e97a73c Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 24 Dec 2025 19:12:06 +0200 Subject: [PATCH 23/62] UI: Extract same method in cf for service --- .../calculated-field-form.service.ts | 94 +++++++++++++++++++ .../calculated-field.component.ts | 74 ++++----------- .../calculated-field-dialog.component.html | 30 ++---- .../calculated-field-dialog.component.ts | 78 ++++----------- .../entity/entity-table-component.models.ts | 9 +- .../calculated-fields-tabs.component.ts | 10 +- .../entity/entity-select.component.ts | 30 ++++-- 7 files changed, 179 insertions(+), 146 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts new file mode 100644 index 0000000000..6ef02f0cf8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts @@ -0,0 +1,94 @@ +/// +/// 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 { DestroyRef, inject, Injectable } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { pairwise, switchMap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { + CalculatedField, + CalculatedFieldConfiguration, + CalculatedFieldEventArguments, + CalculatedFieldType, + OutputStrategyType +} from '@shared/models/calculated-field.models'; +import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; +import { isDefined } from '@core/utils'; +import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; +import { CalculatedFieldsTableEntity } from '@home/components/calculated-fields/calculated-fields-table-config'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { EntityType } from '@shared/models/entity-type.models'; + +@Injectable({ providedIn: 'root' }) +export class CalculatedFieldFormService { + private fb = inject(FormBuilder); + private calculatedFieldsService = inject(CalculatedFieldsService); + + buildForm(): FormGroup { + return this.fb.group({ + name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]], + entityId: [{type: EntityType.DEVICE_PROFILE, id: null}, Validators.required], + type: [CalculatedFieldType.SIMPLE], + debugSettings: [], + configuration: this.fb.control({} as CalculatedFieldConfiguration), + }); + } + + setupTypeChange(form: FormGroup, destroyRef: DestroyRef, isEditActive?: () => boolean): void { + form.get('type').valueChanges.pipe( + pairwise(), + takeUntilDestroyed(destroyRef) + ).subscribe(([prevType, nextType]) => { + const shouldCheck = isEditActive ? isEditActive() : true; + if (shouldCheck) { + if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || + ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { + form.get('configuration').setValue({} as CalculatedFieldConfiguration, { emitEvent: false }); + } + } + }); + } + + prepareConfig(configuration: CalculatedFieldConfiguration): CalculatedFieldConfiguration { + const config = configuration || ({} as CalculatedFieldConfiguration); + if (config.type !== CalculatedFieldType.ALARM) { + if (isDefined(config?.output) && !config?.output?.strategy) { + config.output.strategy = { type: OutputStrategyType.RULE_CHAIN }; + } + } + return config; + } + + testScript( + calculatedFieldId: string, + formValue: CalculatedField, + testDialogFn: (calculatedField: CalculatedFieldsTableEntity, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit?: boolean, expression?: string) => Observable, + destroyRef: DestroyRef, + expression?: string, + ): Observable { + if (calculatedFieldId) { + return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) + .pipe( + switchMap(event => { + const args = event?.arguments ? JSON.parse(event.arguments) : null; + return testDialogFn(formValue, args, false, expression); + }), + takeUntilDestroyed(destroyRef) + ); + } + return testDialogFn(formValue, null, false, expression); + } +} \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts index 308e350116..01d7a8de7a 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts @@ -14,11 +14,11 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, DestroyRef, Inject, Input } from '@angular/core'; +import { ChangeDetectorRef, Component, DestroyRef, inject, Inject, Input } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { EntityType } from '@shared/models/entity-type.models'; import { TranslateService } from '@ngx-translate/core'; import { @@ -27,23 +27,18 @@ import { calculatedFieldsEntityTypeList, CalculatedFieldType, calculatedFieldTypes, - CalculatedFieldTypeTranslations, - OutputStrategyType + CalculatedFieldTypeTranslations } from '@shared/models/calculated-field.models'; -import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; -import { isDefined } from '@core/utils'; import { EntityId } from '@shared/models/id/entity-id'; -import { pairwise, switchMap } from 'rxjs/operators'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { BaseData } from '@shared/models/base-data'; import { Observable } from 'rxjs'; -import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { CalculatedFieldsTableConfig, CalculatedFieldsTableEntity } from '@home/components/calculated-fields/calculated-fields-table-config'; import { TenantId } from '@shared/models/id/tenant-id'; +import { CalculatedFieldFormService } from '@home/components/calculated-fields/calculated-field-form.service'; @Component({ selector: 'tb-calculated-field', @@ -68,14 +63,15 @@ export class CalculatedFieldComponent extends EntityComponent, protected translate: TranslateService, @Inject('entity') protected entityValue: CalculatedFieldInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig, protected fb: FormBuilder, - protected cd: ChangeDetectorRef, - private destroyRef: DestroyRef, - private calculatedFieldsService: CalculatedFieldsService) { + protected cd: ChangeDetectorRef) { super(store, fb, entityValue, entitiesTableConfigValue, cd); } @@ -111,60 +107,30 @@ export class CalculatedFieldComponent extends EntityComponent({} as CalculatedFieldConfiguration), - }); - form.get('type').valueChanges.pipe( - pairwise(), - takeUntilDestroyed(this.destroyRef) - ).subscribe(([prevType, nextType]) => { - if (this.isEditValue) { - if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || - ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { - form.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false}); - } - } - }); + buildForm(_entity?: CalculatedFieldInfo): FormGroup { + const form = inject(CalculatedFieldFormService).buildForm(); + inject(CalculatedFieldFormService).setupTypeChange(form, inject(DestroyRef), () => this.isEditValue); return form; } updateForm(entity: CalculatedFieldInfo) { const { configuration = {} as CalculatedFieldConfiguration, type = CalculatedFieldType.SIMPLE, debugSettings = { failuresEnabled: true, allEnabled: true }, entityId = this.entityId, ...value } = entity ?? {}; - if (configuration.type !== CalculatedFieldType.ALARM) { - if (isDefined(configuration?.output) && !configuration?.output?.strategy) { - configuration.output.strategy = {type: OutputStrategyType.RULE_CHAIN}; - } - } + const preparedConfig = this.cfFormService.prepareConfig(configuration); this.entityForm.patchValue({ type }, {emitEvent: false}); setTimeout(() => { - this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false}); + this.entityForm.patchValue({ configuration: preparedConfig, debugSettings, entityId, ...value }, {emitEvent: false}); this.entityForm.get('type').updateValueAndValidity({onlySelf: true}); }); - if (!entityId) { - this.entityForm.get('configuration').disable({emitEvent: false}); - this.disabledConfiguration = true; - } } onTestScript(expression?: string): Observable { - const calculatedFieldId = this.entity?.id?.id; - if (calculatedFieldId) { - return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) - .pipe( - switchMap(event => { - const args = event?.arguments ? JSON.parse(event.arguments) : null; - return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), args, false, expression); - }), - takeUntilDestroyed(this.destroyRef) - ) - } - - return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), null, false, expression); + return this.cfFormService.testScript( + this.entity?.id?.id, + this.entityFormValue(), + this.entitiesTableConfig.getTestScriptDialog.bind(this.entitiesTableConfig), + this.destroyRef, + expression + ); } updateFormState() { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html index 3d669b02c6..24dd114963 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html @@ -56,29 +56,13 @@ />
@if (!data.entityId) { -
- - - @if (fieldFormGroup.get('entityId.entityType').value) { - - } -
+ + } {{ 'common.type' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts index 4d2df6e416..3370e65f5b 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts @@ -14,11 +14,10 @@ /// limitations under the License. /// -import { Component, DestroyRef, Inject, ViewChild, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, Inject, ViewEncapsulation } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@shared/components/dialog.component'; import { @@ -28,21 +27,18 @@ import { CalculatedFieldTestScriptFn, CalculatedFieldType, calculatedFieldTypes, - CalculatedFieldTypeTranslations, - OutputStrategyType + CalculatedFieldTypeTranslations } from '@shared/models/calculated-field.models'; -import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; -import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; -import { pairwise, switchMap } from 'rxjs/operators'; +import { EntityType } from '@shared/models/entity-type.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { Observable } from 'rxjs'; import { EntityId } from '@shared/models/id/entity-id'; import { AdditionalDebugActionConfig } from '@home/components/entity/debug/entity-debug-settings.model'; -import { deepTrim, isDefined } from '@core/utils'; -import { EntityTypeSelectComponent } from '@shared/components/entity/entity-type-select.component'; -import { EntityAutocompleteComponent } from '@shared/components/entity/entity-autocomplete.component'; +import { deepTrim } from '@core/utils'; import { BaseData } from '@shared/models/base-data'; +import { CalculatedFieldFormService } from '@home/components/calculated-fields/calculated-field-form.service'; +import { FormGroup } from '@angular/forms'; export interface CalculatedFieldDialogData { value?: CalculatedField; @@ -64,16 +60,7 @@ export interface CalculatedFieldDialogData { }) export class CalculatedFieldDialogComponent extends DialogComponent { - fieldFormGroup = this.fb.group({ - name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]], - entityId: this.fb.group({ - entityType: this.fb.control(EntityType.DEVICE_PROFILE, Validators.required), - id: [null as null | string, Validators.required], - }), - type: [CalculatedFieldType.SIMPLE], - debugSettings: [], - configuration: this.fb.control({} as CalculatedFieldConfiguration), - }); + fieldFormGroup: FormGroup; additionalDebugActionConfig = this.data.value?.id ? { ...this.data.additionalDebugActionConfig, @@ -91,18 +78,16 @@ export class CalculatedFieldDialogComponent extends DialogComponent, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: CalculatedFieldDialogData, protected dialogRef: MatDialogRef, private calculatedFieldsService: CalculatedFieldsService, private destroyRef: DestroyRef, - private fb: FormBuilder) { + private cfFormService: CalculatedFieldFormService) { super(store, router, dialogRef); - this.observeType(); + this.fieldFormGroup = this.cfFormService.buildForm(); + this.cfFormService.setupTypeChange(this.fieldFormGroup, this.destroyRef); this.applyDialogData(); if (this.data.isDirty) { @@ -110,7 +95,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent { this.disabledConfiguration = !entityId; @@ -142,24 +127,17 @@ export class CalculatedFieldDialogComponent extends DialogComponent { - const calculatedFieldId = this.data.value?.id?.id; - if (calculatedFieldId) { - return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) - .pipe( - switchMap(event => { - const args = event?.arguments ? JSON.parse(event.arguments) : null; - return this.data.getTestScriptDialogFn(this.fromGroupValue, args, false, expression); - }), - takeUntilDestroyed(this.destroyRef) - ) - } - return this.data.getTestScriptDialogFn(this.fromGroupValue, null, false, expression); + return this.cfFormService.testScript( + this.data.value?.id?.id, + this.fromGroupValue, + this.data.getTestScriptDialogFn, + this.destroyRef, + expression + ); } changeEntity(entity: BaseData): void { @@ -172,28 +150,12 @@ export class CalculatedFieldDialogComponent extends DialogComponent this.fieldFormGroup.get('type').updateValueAndValidity({onlySelf: true})); if (!this.data.entityId) { this.fieldFormGroup.get('configuration').disable({emitEvent: false}); this.disabledConfiguration = true; } } - - private observeType(): void { - this.fieldFormGroup.get('type').valueChanges.pipe( - pairwise(), - takeUntilDestroyed(this.destroyRef) - ).subscribe(([prevType, nextType]) => { - if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || - ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { - this.fieldFormGroup.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false}); - } - }); - } } diff --git a/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts b/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts index e3d0bb19e6..9e2d22f8a9 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts @@ -20,18 +20,22 @@ import { SafeHtml } from '@angular/platform-browser'; import { PageLink } from '@shared/models/page/page-link'; import { Timewindow } from '@shared/models/time/time.models'; import { EntitiesDataSource } from '@home/models/datasource/entity-datasource'; -import { ElementRef, EventEmitter, Renderer2, ViewContainerRef } from '@angular/core'; +import { ElementRef, EventEmitter, ViewContainerRef } from '@angular/core'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { EntityAction } from '@home/models/entity/entity-component.models'; import { - CellActionDescriptor, EntityActionTableColumn, EntityColumn, EntityTableColumn, + CellActionDescriptor, + EntityActionTableColumn, + EntityColumn, + EntityTableColumn, EntityTableConfig, GroupActionDescriptor, HeaderActionDescriptor } from '@home/models/entity/entities-table-config.models'; import { ActivatedRoute } from '@angular/router'; +import type { EntityDetailsPanelComponent } from '@home/components/entity/entity-details-panel.component'; export type EntitiesTableAction = 'add'; @@ -64,6 +68,7 @@ export interface IEntitiesTableComponent { paginator: MatPaginator; sort: MatSort; route: ActivatedRoute; + entityDetailsPanel: EntityDetailsPanelComponent; viewContainerRef: ViewContainerRef; addEnabled(): boolean; diff --git a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts index 0c41ddc33d..54e26db1be 100644 --- a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts @@ -42,8 +42,16 @@ export class CalculatedFieldsTabsComponent extends EntityTabsComponent { + (this.entitiesTableConfig as CalculatedFieldsTableConfig).getTable(); + const entityDetailsPanel = this.entitiesTableConfig.getTable().entityDetailsPanel; + entityDetailsPanel.onToggleEditMode(true); + entityDetailsPanel.selectedTab = 0; + setTimeout(() => { + entityDetailsPanel.detailsForm.get('configuration').setValue({...this.entity.configuration, expression}); + entityDetailsPanel.detailsForm.get('configuration').markAsDirty(); + }); }); }; } diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index fbfb7ae453..57d2380705 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { AfterViewInit, Component, DestroyRef, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; +import { Component, DestroyRef, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -38,7 +38,7 @@ import { BaseData } from '@shared/models/base-data'; multi: true }] }) -export class EntitySelectComponent implements ControlValueAccessor, OnInit, AfterViewInit { +export class EntitySelectComponent implements ControlValueAccessor, OnInit { entitySelectFormGroup: UntypedFormGroup; @@ -70,6 +70,9 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte @Input() filterAllowedEntityTypes: boolean; + @Input() + defaultEntityType: AliasEntityType | EntityType; + @Output() entityChanged = new EventEmitter>(); @@ -81,8 +84,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte AliasEntityType.CURRENT_TENANT, AliasEntityType.CURRENT_USER, AliasEntityType.CURRENT_USER_OWNER ]); - private readonly defaultEntityType: EntityType | AliasEntityType = null; - private propagateChange = (v: any) => { }; constructor(private store: Store, @@ -93,15 +94,18 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte const entityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, this.useAliasEntityTypes); + + let defaultEntityType: EntityType | AliasEntityType = null + if (entityTypes.length === 1) { this.displayEntityTypeSelect = false; - this.defaultEntityType = entityTypes[0]; + defaultEntityType = entityTypes[0]; } else { this.displayEntityTypeSelect = true; } this.entitySelectFormGroup = this.fb.group({ - entityType: [this.defaultEntityType], + entityType: [defaultEntityType], entityId: [null] }); } @@ -133,9 +137,19 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte if (additionNullUIIDEntityTypes.length > 0) { additionNullUIIDEntityTypes.forEach((entityType) => this.entityTypeNullUUID.add(entityType)); } - } - ngAfterViewInit(): void { + if (this.filterAllowedEntityTypes === false) { + if (this.allowedEntityTypes?.length === 1) { + this.displayEntityTypeSelect = false; + this.entitySelectFormGroup.get('entityType').setValue(this.allowedEntityTypes[0]) + } else { + this.displayEntityTypeSelect = true; + } + } + + if (this.defaultEntityType) { + this.entitySelectFormGroup.get('entityType').setValue(this.defaultEntityType); + } } setDisabledState(isDisabled: boolean): void { From f2dadd07914d6c327756160f0c665fce8597a786 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 25 Dec 2025 11:52:53 +0200 Subject: [PATCH 24/62] Version set to 4.2.1.2-SNAPSHOT --- application/pom.xml | 2 +- .../service/install/DefaultDatabaseSchemaSettingsService.java | 2 +- common/actor/pom.xml | 2 +- common/cache/pom.xml | 2 +- common/cluster-api/pom.xml | 2 +- common/coap-server/pom.xml | 2 +- common/dao-api/pom.xml | 2 +- common/data/pom.xml | 2 +- common/discovery-api/pom.xml | 2 +- common/edge-api/pom.xml | 2 +- common/edqs/pom.xml | 2 +- common/message/pom.xml | 2 +- common/pom.xml | 2 +- common/proto/pom.xml | 2 +- common/queue/pom.xml | 2 +- common/script/pom.xml | 2 +- common/script/remote-js-client/pom.xml | 2 +- common/script/script-api/pom.xml | 2 +- common/stats/pom.xml | 2 +- common/transport/coap/pom.xml | 2 +- common/transport/http/pom.xml | 2 +- common/transport/lwm2m/pom.xml | 2 +- common/transport/mqtt/pom.xml | 2 +- common/transport/pom.xml | 2 +- common/transport/snmp/pom.xml | 2 +- common/transport/transport-api/pom.xml | 2 +- common/util/pom.xml | 2 +- common/version-control/pom.xml | 2 +- dao/pom.xml | 2 +- edqs/pom.xml | 2 +- monitoring/pom.xml | 2 +- msa/black-box-tests/pom.xml | 2 +- msa/edqs/pom.xml | 2 +- msa/js-executor/package.json | 2 +- msa/js-executor/pom.xml | 2 +- msa/monitoring/pom.xml | 2 +- msa/pom.xml | 2 +- msa/tb-node/pom.xml | 2 +- msa/tb/pom.xml | 2 +- msa/transport/coap/pom.xml | 2 +- msa/transport/http/pom.xml | 2 +- msa/transport/lwm2m/pom.xml | 2 +- msa/transport/mqtt/pom.xml | 2 +- msa/transport/pom.xml | 2 +- msa/transport/snmp/pom.xml | 2 +- msa/vc-executor-docker/pom.xml | 2 +- msa/vc-executor/pom.xml | 2 +- msa/web-ui/package.json | 2 +- msa/web-ui/pom.xml | 2 +- netty-mqtt/pom.xml | 4 ++-- pom.xml | 2 +- rest-client/pom.xml | 2 +- rule-engine/pom.xml | 2 +- rule-engine/rule-engine-api/pom.xml | 2 +- rule-engine/rule-engine-components/pom.xml | 2 +- tools/pom.xml | 2 +- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/lwm2m/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- transport/pom.xml | 2 +- transport/snmp/pom.xml | 2 +- ui-ngx/package.json | 2 +- ui-ngx/pom.xml | 2 +- 64 files changed, 65 insertions(+), 65 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 47772dfa97..9f3b79b3f5 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard application diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java index 44aa14b782..500ad60df0 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java @@ -30,7 +30,7 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti // This list should include all versions that are compatible for the upgrade in 4 digits format (like 4.2.0.0, etc.). // The compatibility cycle usually breaks when we have some scripts written in Java that may not work after a new release. - private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.2.0.0", "4.2.1.0"); + private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.2.1.0"); private final ProjectInfo projectInfo; private final JdbcTemplate jdbcTemplate; diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 6411934f7d..68d05cecba 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index a43009b6e1..3d42e467ba 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index 9a75a6b0c3..f9b0b60e50 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index 1950a9ecc2..fd53b3dfc7 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 8b0ad31e23..fe6f59503b 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index a574a6ed07..e8bd87aa19 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/discovery-api/pom.xml b/common/discovery-api/pom.xml index 9e65abf10a..2c62c714f1 100644 --- a/common/discovery-api/pom.xml +++ b/common/discovery-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index 208e1e7b55..704dc7759b 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/edqs/pom.xml b/common/edqs/pom.xml index 677f5ad5ae..bd2b96da15 100644 --- a/common/edqs/pom.xml +++ b/common/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index f70bc260cf..a3a4d2e38e 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index 5467f452d2..76aa577782 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard common diff --git a/common/proto/pom.xml b/common/proto/pom.xml index bab2f8d12b..875904824b 100644 --- a/common/proto/pom.xml +++ b/common/proto/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 19368c1345..f88baefc6e 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/script/pom.xml b/common/script/pom.xml index 4b7a812737..cb4d0c2ac2 100644 --- a/common/script/pom.xml +++ b/common/script/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml index 45d4698efe..e2dffb44a6 100644 --- a/common/script/remote-js-client/pom.xml +++ b/common/script/remote-js-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT script org.thingsboard.common.script diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml index 3bc2168dfe..c10c49b279 100644 --- a/common/script/script-api/pom.xml +++ b/common/script/script-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT script org.thingsboard.common.script diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 66011657d8..8e896767c7 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index e9d5dcfc1a..29bf5c7567 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index caff518fe2..3b033bb741 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 2bec8f7bcb..8c4e9e79ff 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 18ff4e96c3..7012f22bb4 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index feaef2bcce..d6f1761a40 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index da5093349d..522034b9c8 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 1cb128b439..34de935e17 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index 6509f154ad..2e1eb18478 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index 939d0d47ad..1930c026b5 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index 5044c0edfb..36e5bad26d 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard dao diff --git a/edqs/pom.xml b/edqs/pom.xml index ef91e0bed4..2f5e513f16 100644 --- a/edqs/pom.xml +++ b/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard edqs diff --git a/monitoring/pom.xml b/monitoring/pom.xml index 3fb4ba5314..1b67c4c397 100644 --- a/monitoring/pom.xml +++ b/monitoring/pom.xml @@ -21,7 +21,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 6800c0c4cf..0d560720c8 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/edqs/pom.xml b/msa/edqs/pom.xml index 62374eb2bc..a40ef45fff 100644 --- a/msa/edqs/pom.xml +++ b/msa/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index 3f4370b642..3c77172f4e 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-js-executor", "private": true, - "version": "4.2.1.1", + "version": "4.2.1.2", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 3aaf380e2f..99cef90704 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/monitoring/pom.xml b/msa/monitoring/pom.xml index b81abce27d..cd7615ae6e 100644 --- a/msa/monitoring/pom.xml +++ b/msa/monitoring/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT msa diff --git a/msa/pom.xml b/msa/pom.xml index 5eff7cc06d..29299d7b9a 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index e473e1dfdf..0be77548a2 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 9db071c9ae..e46152fb62 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 098e3263b8..7b9167980d 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 3acbeb7a2d..e56c364c72 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index bc6d0d004e..7a2fc4b007 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index ef20c96f26..7c0c77725d 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index 76cec909da..e1d53efaf4 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index 348880019c..ba25ffa004 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT org.thingsboard.msa.transport diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml index b1c11efe0a..134c3d8ce5 100644 --- a/msa/vc-executor-docker/pom.xml +++ b/msa/vc-executor-docker/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml index a59a8aa823..c487ddc721 100644 --- a/msa/vc-executor/pom.xml +++ b/msa/vc-executor/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index 97d69546bc..44aade2d73 100644 --- a/msa/web-ui/package.json +++ b/msa/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-web-ui", "private": true, - "version": "4.2.1.1", + "version": "4.2.1.2", "description": "ThingsBoard Web UI Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 70200eddb3..36e53ed52c 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index c0743024ea..99261b714d 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard netty-mqtt - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 7f8ef8074a..9edccba7f7 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT pom Thingsboard diff --git a/rest-client/pom.xml b/rest-client/pom.xml index eddcd84d9a..3ee5321627 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard rest-client diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 99372b9703..d89c238559 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index c52fb92594..5f48404b91 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index f582f1277a..e307d9a57d 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/tools/pom.xml b/tools/pom.xml index 783dc1ee73..bd16603596 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index f43d31e35b..681c75b102 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 7e55ae3db5..b338a4b4ea 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index f64f8b4aa4..4d5d621a08 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 0bcf68cbb8..336265ff00 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index 1d3755a766..a4b9c9c630 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index 5ea39a4066..a361d85838 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT transport diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 74b4bbbb9c..b17c3e1d35 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -1,6 +1,6 @@ { "name": "thingsboard", - "version": "4.2.1.1", + "version": "4.2.1.2", "scripts": { "ng": "ng", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open", diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index d5bd48de78..54b8adc93a 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.1-RC + 4.2.1.2-SNAPSHOT thingsboard org.thingsboard From 5e8ca1ca46099ca94151a5b32c9c7bef099816a7 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 25 Dec 2025 11:59:03 +0200 Subject: [PATCH 25/62] TMP version set to 4.2.1.2-SNAPSHOT --- application/pom.xml | 2 +- common/actor/pom.xml | 2 +- common/cache/pom.xml | 2 +- common/cluster-api/pom.xml | 2 +- common/coap-server/pom.xml | 2 +- common/dao-api/pom.xml | 2 +- common/data/pom.xml | 2 +- common/discovery-api/pom.xml | 2 +- common/edge-api/pom.xml | 2 +- common/edqs/pom.xml | 2 +- common/message/pom.xml | 2 +- common/pom.xml | 2 +- common/proto/pom.xml | 2 +- common/queue/pom.xml | 2 +- common/script/pom.xml | 2 +- common/script/remote-js-client/pom.xml | 2 +- common/script/script-api/pom.xml | 2 +- common/stats/pom.xml | 2 +- common/transport/coap/pom.xml | 2 +- common/transport/http/pom.xml | 2 +- common/transport/lwm2m/pom.xml | 2 +- common/transport/mqtt/pom.xml | 2 +- common/transport/pom.xml | 2 +- common/transport/snmp/pom.xml | 2 +- common/transport/transport-api/pom.xml | 2 +- common/util/pom.xml | 2 +- common/version-control/pom.xml | 2 +- dao/pom.xml | 2 +- edqs/pom.xml | 2 +- monitoring/pom.xml | 2 +- msa/black-box-tests/pom.xml | 2 +- msa/edqs/pom.xml | 2 +- msa/js-executor/pom.xml | 2 +- msa/monitoring/pom.xml | 2 +- msa/pom.xml | 2 +- msa/tb-node/pom.xml | 2 +- msa/tb/pom.xml | 2 +- msa/transport/coap/pom.xml | 2 +- msa/transport/http/pom.xml | 2 +- msa/transport/lwm2m/pom.xml | 2 +- msa/transport/mqtt/pom.xml | 2 +- msa/transport/pom.xml | 2 +- msa/transport/snmp/pom.xml | 2 +- msa/vc-executor-docker/pom.xml | 2 +- msa/vc-executor/pom.xml | 2 +- msa/web-ui/pom.xml | 2 +- netty-mqtt/pom.xml | 4 ++-- pom.xml | 2 +- rest-client/pom.xml | 2 +- rule-engine/pom.xml | 2 +- rule-engine/rule-engine-api/pom.xml | 2 +- rule-engine/rule-engine-components/pom.xml | 2 +- tools/pom.xml | 2 +- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/lwm2m/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- transport/pom.xml | 2 +- transport/snmp/pom.xml | 2 +- ui-ngx/pom.xml | 2 +- 60 files changed, 61 insertions(+), 61 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 7d304226ad..9f3b79b3f5 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard application diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 1ce65906da..68d05cecba 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index bc2fbd8bda..3d42e467ba 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index cbb082d26a..f9b0b60e50 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index d2c5ffc7ab..fd53b3dfc7 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 6904d69aa3..fe6f59503b 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index 921135f811..e8bd87aa19 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/discovery-api/pom.xml b/common/discovery-api/pom.xml index 667c140615..2c62c714f1 100644 --- a/common/discovery-api/pom.xml +++ b/common/discovery-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index 87068df36c..704dc7759b 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/edqs/pom.xml b/common/edqs/pom.xml index aec29584e4..bd2b96da15 100644 --- a/common/edqs/pom.xml +++ b/common/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index 7d4487f74a..a3a4d2e38e 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index 72ec1ee9a6..76aa577782 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard common diff --git a/common/proto/pom.xml b/common/proto/pom.xml index 7230d20ce1..875904824b 100644 --- a/common/proto/pom.xml +++ b/common/proto/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 9885975417..f88baefc6e 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/script/pom.xml b/common/script/pom.xml index efa5efafca..cb4d0c2ac2 100644 --- a/common/script/pom.xml +++ b/common/script/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml index 189d86d3bf..e2dffb44a6 100644 --- a/common/script/remote-js-client/pom.xml +++ b/common/script/remote-js-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.3.0-RC + 4.2.1.2-SNAPSHOT script org.thingsboard.common.script diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml index c80e51a60f..c10c49b279 100644 --- a/common/script/script-api/pom.xml +++ b/common/script/script-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.3.0-RC + 4.2.1.2-SNAPSHOT script org.thingsboard.common.script diff --git a/common/stats/pom.xml b/common/stats/pom.xml index d1bb0a80c3..8e896767c7 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index dbabba721e..29bf5c7567 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index 475effe638..3b033bb741 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index d0831647e8..8c4e9e79ff 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 3ba19f00e5..7012f22bb4 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index b56fbbc428..d6f1761a40 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index b858b8ff5f..522034b9c8 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index db34b1d21f..34de935e17 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index 83febcaef7..2e1eb18478 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index 6207dc3e72..1930c026b5 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index f0aa6b833f..36e5bad26d 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard dao diff --git a/edqs/pom.xml b/edqs/pom.xml index 546da5902f..2f5e513f16 100644 --- a/edqs/pom.xml +++ b/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard edqs diff --git a/monitoring/pom.xml b/monitoring/pom.xml index dbac99d9c4..1b67c4c397 100644 --- a/monitoring/pom.xml +++ b/monitoring/pom.xml @@ -21,7 +21,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 30873c4c50..0d560720c8 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/edqs/pom.xml b/msa/edqs/pom.xml index 1b0084d471..a40ef45fff 100644 --- a/msa/edqs/pom.xml +++ b/msa/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 50ab2286bb..99cef90704 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/monitoring/pom.xml b/msa/monitoring/pom.xml index 2e85f02671..cd7615ae6e 100644 --- a/msa/monitoring/pom.xml +++ b/msa/monitoring/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT msa diff --git a/msa/pom.xml b/msa/pom.xml index f41f7f0fc0..e72d2ccfde 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 29ea35dba5..0be77548a2 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index fd562fc692..e46152fb62 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 6a08c8154c..7b9167980d 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 680863481a..e56c364c72 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index 1226a9d549..7a2fc4b007 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index cfe624c69d..7c0c77725d 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index b63aec5c68..e1d53efaf4 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index b6a72efa58..ba25ffa004 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 4.3.0-RC + 4.2.1.2-SNAPSHOT org.thingsboard.msa.transport diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml index eba717cb36..134c3d8ce5 100644 --- a/msa/vc-executor-docker/pom.xml +++ b/msa/vc-executor-docker/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml index 0635e69bd5..c487ddc721 100644 --- a/msa/vc-executor/pom.xml +++ b/msa/vc-executor/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 1eb47413d7..36e53ed52c 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 01bc357127..99261b714d 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard netty-mqtt - 4.3.0-RC + 4.2.1.2-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 99108196aa..9edccba7f7 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT pom Thingsboard diff --git a/rest-client/pom.xml b/rest-client/pom.xml index fd5783227b..3ee5321627 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard rest-client diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index dce202bc94..d89c238559 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index ef6904b24c..5f48404b91 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 78e6b5e684..e307d9a57d 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/tools/pom.xml b/tools/pom.xml index d76b19ba2d..bd16603596 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 4a30c5d2bb..681c75b102 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index bc1c7a845d..b338a4b4ea 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index 8871a2b2b3..4d5d621a08 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 2fe1cb91b2..336265ff00 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index ad394b46f9..a4b9c9c630 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index 528538bd03..a361d85838 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT transport diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index f83c914409..54b8adc93a 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.3.0-RC + 4.2.1.2-SNAPSHOT thingsboard org.thingsboard From c069d282315da2b63eb9241914433486a29f481d Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 25 Dec 2025 12:04:22 +0200 Subject: [PATCH 26/62] Version set to 4.3.0-RC --- application/pom.xml | 2 +- .../service/install/DefaultDatabaseSchemaSettingsService.java | 3 ++- common/actor/pom.xml | 2 +- common/cache/pom.xml | 2 +- common/cluster-api/pom.xml | 2 +- common/coap-server/pom.xml | 2 +- common/dao-api/pom.xml | 2 +- common/data/pom.xml | 2 +- common/discovery-api/pom.xml | 2 +- common/edge-api/pom.xml | 2 +- common/edqs/pom.xml | 2 +- common/message/pom.xml | 2 +- common/pom.xml | 2 +- common/proto/pom.xml | 2 +- common/queue/pom.xml | 2 +- common/script/pom.xml | 2 +- common/script/remote-js-client/pom.xml | 2 +- common/script/script-api/pom.xml | 2 +- common/stats/pom.xml | 2 +- common/transport/coap/pom.xml | 2 +- common/transport/http/pom.xml | 2 +- common/transport/lwm2m/pom.xml | 2 +- common/transport/mqtt/pom.xml | 2 +- common/transport/pom.xml | 2 +- common/transport/snmp/pom.xml | 2 +- common/transport/transport-api/pom.xml | 2 +- common/util/pom.xml | 2 +- common/version-control/pom.xml | 2 +- dao/pom.xml | 2 +- edqs/pom.xml | 2 +- monitoring/pom.xml | 2 +- msa/black-box-tests/pom.xml | 2 +- msa/edqs/pom.xml | 2 +- msa/js-executor/pom.xml | 2 +- msa/monitoring/pom.xml | 2 +- msa/pom.xml | 4 ++-- msa/tb-node/pom.xml | 2 +- msa/tb/pom.xml | 2 +- msa/transport/coap/pom.xml | 2 +- msa/transport/http/pom.xml | 2 +- msa/transport/lwm2m/pom.xml | 2 +- msa/transport/mqtt/pom.xml | 2 +- msa/transport/pom.xml | 2 +- msa/transport/snmp/pom.xml | 2 +- msa/vc-executor-docker/pom.xml | 2 +- msa/vc-executor/pom.xml | 2 +- msa/web-ui/pom.xml | 2 +- netty-mqtt/pom.xml | 4 ++-- pom.xml | 2 +- rest-client/pom.xml | 2 +- rule-engine/pom.xml | 2 +- rule-engine/rule-engine-api/pom.xml | 2 +- rule-engine/rule-engine-components/pom.xml | 2 +- tools/pom.xml | 2 +- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/lwm2m/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- transport/pom.xml | 2 +- transport/snmp/pom.xml | 2 +- ui-ngx/pom.xml | 2 +- 61 files changed, 64 insertions(+), 63 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 9f3b79b3f5..7d304226ad 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard application diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java index 500ad60df0..d65ac6ba31 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java @@ -30,7 +30,8 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti // This list should include all versions that are compatible for the upgrade in 4 digits format (like 4.2.0.0, etc.). // The compatibility cycle usually breaks when we have some scripts written in Java that may not work after a new release. - private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.2.1.0"); + // TODO: don't check the "patch" number, since upgrade is not required for patch releases + private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.2.1.0", "4.2.1.1", "4.2.1.2"); private final ProjectInfo projectInfo; private final JdbcTemplate jdbcTemplate; diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 68d05cecba..1ce65906da 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index 3d42e467ba..bc2fbd8bda 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index f9b0b60e50..cbb082d26a 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index fd53b3dfc7..d2c5ffc7ab 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index fe6f59503b..6904d69aa3 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index e8bd87aa19..921135f811 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/discovery-api/pom.xml b/common/discovery-api/pom.xml index 2c62c714f1..667c140615 100644 --- a/common/discovery-api/pom.xml +++ b/common/discovery-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index 704dc7759b..87068df36c 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/edqs/pom.xml b/common/edqs/pom.xml index bd2b96da15..aec29584e4 100644 --- a/common/edqs/pom.xml +++ b/common/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index a3a4d2e38e..7d4487f74a 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index 76aa577782..72ec1ee9a6 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard common diff --git a/common/proto/pom.xml b/common/proto/pom.xml index 875904824b..7230d20ce1 100644 --- a/common/proto/pom.xml +++ b/common/proto/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index f88baefc6e..9885975417 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/script/pom.xml b/common/script/pom.xml index cb4d0c2ac2..efa5efafca 100644 --- a/common/script/pom.xml +++ b/common/script/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml index e2dffb44a6..189d86d3bf 100644 --- a/common/script/remote-js-client/pom.xml +++ b/common/script/remote-js-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.2-SNAPSHOT + 4.3.0-RC script org.thingsboard.common.script diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml index c10c49b279..c80e51a60f 100644 --- a/common/script/script-api/pom.xml +++ b/common/script/script-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.2-SNAPSHOT + 4.3.0-RC script org.thingsboard.common.script diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 8e896767c7..d1bb0a80c3 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 29bf5c7567..dbabba721e 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index 3b033bb741..475effe638 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 8c4e9e79ff..d0831647e8 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 7012f22bb4..3ba19f00e5 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index d6f1761a40..b56fbbc428 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index 522034b9c8..b858b8ff5f 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 34de935e17..db34b1d21f 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index 2e1eb18478..83febcaef7 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index 1930c026b5..6207dc3e72 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index 36e5bad26d..f0aa6b833f 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard dao diff --git a/edqs/pom.xml b/edqs/pom.xml index 2f5e513f16..546da5902f 100644 --- a/edqs/pom.xml +++ b/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard edqs diff --git a/monitoring/pom.xml b/monitoring/pom.xml index 1b67c4c397..dbac99d9c4 100644 --- a/monitoring/pom.xml +++ b/monitoring/pom.xml @@ -21,7 +21,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 0d560720c8..30873c4c50 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC msa org.thingsboard.msa diff --git a/msa/edqs/pom.xml b/msa/edqs/pom.xml index a40ef45fff..1b0084d471 100644 --- a/msa/edqs/pom.xml +++ b/msa/edqs/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC msa org.thingsboard.msa diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 99cef90704..50ab2286bb 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC msa org.thingsboard.msa diff --git a/msa/monitoring/pom.xml b/msa/monitoring/pom.xml index cd7615ae6e..2e85f02671 100644 --- a/msa/monitoring/pom.xml +++ b/msa/monitoring/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC msa diff --git a/msa/pom.xml b/msa/pom.xml index 8b19c757b1..8207996169 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard msa @@ -133,7 +133,7 @@ - 4.2.1-latest + 4.3.0-latest false diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 0be77548a2..29ea35dba5 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index e46152fb62..fd562fc692 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 7b9167980d..6a08c8154c 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index e56c364c72..680863481a 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index 7a2fc4b007..1226a9d549 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index 7c0c77725d..cfe624c69d 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index e1d53efaf4..b63aec5c68 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index ba25ffa004..b6a72efa58 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 4.2.1.2-SNAPSHOT + 4.3.0-RC org.thingsboard.msa.transport diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml index 134c3d8ce5..eba717cb36 100644 --- a/msa/vc-executor-docker/pom.xml +++ b/msa/vc-executor-docker/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC msa org.thingsboard.msa diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml index c487ddc721..0635e69bd5 100644 --- a/msa/vc-executor/pom.xml +++ b/msa/vc-executor/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC msa org.thingsboard.msa diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 36e53ed52c..1eb47413d7 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 99261b714d..01bc357127 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard netty-mqtt - 4.2.1.2-SNAPSHOT + 4.3.0-RC jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 9edccba7f7..99108196aa 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC pom Thingsboard diff --git a/rest-client/pom.xml b/rest-client/pom.xml index 3ee5321627..fd5783227b 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard rest-client diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index d89c238559..dce202bc94 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 5f48404b91..ef6904b24c 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index e307d9a57d..78e6b5e684 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC rule-engine org.thingsboard.rule-engine diff --git a/tools/pom.xml b/tools/pom.xml index bd16603596..d76b19ba2d 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 681c75b102..4a30c5d2bb 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index b338a4b4ea..bc1c7a845d 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.transport diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index 4d5d621a08..8871a2b2b3 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 336265ff00..2fe1cb91b2 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index a4b9c9c630..ad394b46f9 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index a361d85838..528538bd03 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC transport diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index 54b8adc93a..f83c914409 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 4.2.1.2-SNAPSHOT + 4.3.0-RC thingsboard org.thingsboard From b72f6a8fd18aef8f008fe1bccac6311747b75d99 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 25 Dec 2025 12:41:32 +0200 Subject: [PATCH 27/62] Set default base image to thingsboard/openjdk25:trixie-slim --- msa/pom.xml | 2 +- msa/tb-node/docker/Dockerfile | 15 +++++++++++++++ msa/tb/docker-cassandra/Dockerfile | 17 ++++++++++++++++- msa/tb/docker-postgres/Dockerfile | 17 ++++++++++++++++- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/msa/pom.xml b/msa/pom.xml index 8b19c757b1..871026cc16 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -32,7 +32,7 @@ ${basedir}/.. thingsboard - thingsboard/openjdk17:bookworm-slim + thingsboard/openjdk25:trixie-slim true true 1.4.13 diff --git a/msa/tb-node/docker/Dockerfile b/msa/tb-node/docker/Dockerfile index c084c9c15b..a588142152 100644 --- a/msa/tb-node/docker/Dockerfile +++ b/msa/tb-node/docker/Dockerfile @@ -16,6 +16,21 @@ FROM ${docker.base.image} +# For SVG renderer Debian Trixie slim fonts should be installed explicitly +RUN echo "Previously installed font libraries:" \ + && (dpkg -l | grep -E 'font|dejavu|harfbuzz' || echo " (none)") \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + libharfbuzz0b fontconfig fonts-dejavu-core \ + && rm -rf /var/lib/apt/lists/* \ + && echo "Current fonts libraries:" \ + && (dpkg -l | grep -E 'font|dejavu|harfbuzz') \ + && echo "Font config list:" \ + && fc-list && test -n "$(fc-list)" \ + && (fc-list | grep -i dejavu) \ + && echo "Harfbuzz library test with ldconfig:" \ + && (ldconfig -p | grep harfbuzz) + COPY logback.xml start-tb-node.sh ${pkg.name}.deb /tmp/ RUN chmod a+x /tmp/*.sh \ diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index 20a0d8cf7a..d530f54152 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bookworm-slim +FROM ${docker.base.image} ENV PG_MAJOR=16 @@ -40,6 +40,21 @@ ENV PATH=$PATH:/usr/lib/postgresql/$PG_MAJOR/bin ENV PGLOG=/var/log/postgres ENV CASSANDRA_LOG=/var/log/cassandra +# For SVG renderer Debian Trixie slim fonts should be installed explicitly +RUN echo "Previously installed font libraries:" \ + && (dpkg -l | grep -E 'font|dejavu|harfbuzz' || echo " (none)") \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + libharfbuzz0b fontconfig fonts-dejavu-core \ + && rm -rf /var/lib/apt/lists/* \ + && echo "Current fonts libraries:" \ + && (dpkg -l | grep -E 'font|dejavu|harfbuzz') \ + && echo "Font config list:" \ + && fc-list && test -n "$(fc-list)" \ + && (fc-list | grep -i dejavu) \ + && echo "Harfbuzz library test with ldconfig:" \ + && (ldconfig -p | grep harfbuzz) + COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/ RUN apt-get update \ diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile index 7c66d626c7..0e16900a14 100644 --- a/msa/tb/docker-postgres/Dockerfile +++ b/msa/tb/docker-postgres/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bookworm-slim +FROM ${docker.base.image} ENV PG_MAJOR 12 @@ -32,6 +32,21 @@ ENV SPRING_DATASOURCE_USERNAME=${pkg.user} ENV PGLOG=/var/log/postgres +# For SVG renderer Debian Trixie slim fonts should be installed explicitly +RUN echo "Previously installed font libraries:" \ + && (dpkg -l | grep -E 'font|dejavu|harfbuzz' || echo " (none)") \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + libharfbuzz0b fontconfig fonts-dejavu-core \ + && rm -rf /var/lib/apt/lists/* \ + && echo "Current fonts libraries:" \ + && (dpkg -l | grep -E 'font|dejavu|harfbuzz') \ + && echo "Font config list:" \ + && fc-list && test -n "$(fc-list)" \ + && (fc-list | grep -i dejavu) \ + && echo "Harfbuzz library test with ldconfig:" \ + && (ldconfig -p | grep harfbuzz) + COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/ RUN apt-get update \ From 86626834b01fd0851f89c794515fc7e3b10479c5 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 25 Dec 2025 14:50:47 +0200 Subject: [PATCH 28/62] Fix occasional alarm rule test failure due to race condition --- .../cf/ctx/state/alarm/AlarmCalculatedFieldState.java | 3 ++- .../server/service/cf/ctx/state/alarm/AlarmRuleState.java | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java index 4f2cbede09..8c258d0b00 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java @@ -150,7 +150,8 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState { clearRuleState = null; } } - log.debug("Initialized create rule states {} and clear rule state {} for {}", createRuleStates, clearRuleState, configuration); + log.debug("Initialized create rule states {} and clear rule state {} for {}. Restored: {}, reeval needed: {}", + createRuleStates, clearRuleState, configuration, restored, reevalNeeded); if (reevalNeeded.get()) { initCurrentAlarm(ctx); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmRuleState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmRuleState.java index 569bbe9310..cf7b1ce108 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmRuleState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmRuleState.java @@ -262,6 +262,14 @@ public class AlarmRuleState { } } + public void setDurationCheckFuture(ScheduledFuture durationCheckFuture) { + if (this.durationCheckFuture != null) { + log.warn("Setting new duration check future while previous is not null for state {}", this, new RuntimeException("stacktrace")); + this.durationCheckFuture.cancel(true); + } + this.durationCheckFuture = durationCheckFuture; + } + public boolean isEmpty() { return eventCount == 0L && firstEventTs == 0L && lastCheckTs == 0L && durationCheckFuture == null; } From e083aaa4c3691c85c938470eaea7c3910c9bc83a Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 26 Dec 2025 11:34:43 +0200 Subject: [PATCH 29/62] UI: Fixed calculated fields panel dirty state --- .../calculated-fields/calculated-field.component.ts | 3 +-- .../calculated-field-arguments-table.component.ts | 5 +++-- ...alculated-field-geofencing-zone-groups-table.component.ts | 4 ++-- .../metrics/calculated-field-metrics-table.component.ts | 4 ++-- .../components/output/calculated-field-output.component.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts index 01d7a8de7a..01cc53609d 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts @@ -116,10 +116,9 @@ export class CalculatedFieldComponent extends EntityComponent { this.entityForm.patchValue({ configuration: preparedConfig, debugSettings, entityId, ...value }, {emitEvent: false}); - this.entityForm.get('type').updateValueAndValidity({onlySelf: true}); }); } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts index 9a8395a557..89c87c8e27 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts @@ -239,7 +239,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces } writeValue(argumentsObj: Record): void { - this.argumentsFormArray.clear(); + this.argumentsFormArray.clear({emitEvent: false}); this.populateArgumentsFormArray(argumentsObj); this.updateEntityNameMap(this.argumentsFormArray.value); } @@ -264,7 +264,8 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces }; this.argumentsFormArray.push(this.fb.control(value), { emitEvent: false }); }); - this.argumentsFormArray.updateValueAndValidity(); + this.updateDataSource(this.argumentsFormArray.value); + // this.argumentsFormArray.updateValueAndValidity({emitEvent: false}); } private updateEntityNameMap(values: CalculatedFieldArgumentValue[]): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts index 228d08fd90..e102033f18 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts @@ -214,7 +214,7 @@ export class CalculatedFieldGeofencingZoneGroupsTableComponent implements Contro } writeValue(zonesObj: Record): void { - this.zoneGroupsFormArray.clear(); + this.zoneGroupsFormArray.clear({emitEvent: false}); this.populateZonesFormArray(zonesObj); this.updateEntityNameMap(this.zoneGroupsFormArray.value); } @@ -231,7 +231,7 @@ export class CalculatedFieldGeofencingZoneGroupsTableComponent implements Contro }; this.zoneGroupsFormArray.push(this.fb.control(value), { emitEvent: false }); }); - this.zoneGroupsFormArray.updateValueAndValidity(); + this.updateDataSource(this.zoneGroupsFormArray.value); } private updateEntityNameMap(values: CalculatedFieldGeofencingValue[]): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts index 21b21d07ac..531f8d1014 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts @@ -218,7 +218,7 @@ export class CalculatedFieldMetricsTableComponent implements OnInit, ControlValu } writeValue(metrics: Record): void { - this.metricsFormArray.clear(); + this.metricsFormArray.clear({emitEvent: false}); this.populateZonesFormArray(metrics); } @@ -230,7 +230,7 @@ export class CalculatedFieldMetricsTableComponent implements OnInit, ControlValu }; this.metricsFormArray.push(this.fb.control(value), { emitEvent: false }); }); - this.metricsFormArray.updateValueAndValidity(); + this.updateDataSource(this.metricsFormArray.value); } private getSortValue(metric: CalculatedFieldAggMetricValue, column: string): string { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts index 00fea861f6..63e3faf131 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts @@ -253,7 +253,7 @@ export class CalculatedFieldOutputComponent implements ControlValueAccessor, Val private updateTimeSeriesTtl(value: boolean) { if (value) { - this.outputForm.get('strategy.useCustomTtl').enable({emitEvent: true}); + this.outputForm.get('strategy.useCustomTtl').enable({emitEvent: false}); } else { this.outputForm.get('strategy.useCustomTtl').disable({emitEvent: false}); this.outputForm.get('strategy.ttl').disable({emitEvent: false}); From 3c5df439ccbbe228ef74c85f1c9984cd323c01ed Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 26 Dec 2025 12:42:23 +0200 Subject: [PATCH 30/62] UI: Fixed minor problem in calculated fields panel --- .../calculated-fields-table-config.ts | 23 ++++++++++++++++--- ...culated-field-arguments-table.component.ts | 3 +-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts index 5841d9e511..e5e4063c21 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts @@ -35,7 +35,7 @@ import { DestroyRef, Renderer2 } from '@angular/core'; import { EntityDebugSettings } from '@shared/models/entity.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; -import { catchError, filter, switchMap, tap } from 'rxjs/operators'; +import { catchError, filter, first, switchMap, tap } from 'rxjs/operators'; import { ArgumentEntityType, ArgumentType, @@ -175,7 +175,8 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig true, - onAction: ($event, entity) => this.openDebugEventsDialog($event, entity), + onAction: ($event, entity) => + this.pageMode ? this.openDebugTab($event, entity) : this.openDebugEventsDialog($event, entity), }, { name: '', @@ -202,7 +203,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig 1) { + table.entityDetailsPanel.matTabGroup.selectedIndex = 1; + } else { + table.entityDetailsPanel.matTabGroup._tabs.changes.pipe( + first() + ).subscribe(() => { + table.entityDetailsPanel.matTabGroup.selectedIndex = 1; + }) + } + } + } + private editCalculatedField($event: Event, calculatedField: CalculatedFieldsTableEntity, isDirty = false): void { $event?.stopPropagation(); this.getCalculatedFieldDialog(calculatedField, 'action.apply', isDirty) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts index 89c87c8e27..983609dfa7 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts @@ -249,7 +249,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces } protected changeIsScriptMode(): void { - this.argumentsFormArray.updateValueAndValidity(); + this.argumentsFormArray.updateValueAndValidity({emitEvent: !this.disable}); } protected isEditButtonShowBadge(argument: CalculatedFieldArgumentValue): boolean { @@ -265,7 +265,6 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces this.argumentsFormArray.push(this.fb.control(value), { emitEvent: false }); }); this.updateDataSource(this.argumentsFormArray.value); - // this.argumentsFormArray.updateValueAndValidity({emitEvent: false}); } private updateEntityNameMap(values: CalculatedFieldArgumentValue[]): void { From 96f2c340588d959f84b8b762e431fbcf37dcbfc4 Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Thu, 25 Dec 2025 13:04:34 +0200 Subject: [PATCH 31/62] UI: Alarm rule details page --- .../alarm-rules/alarm-rule.module.ts | 5 +- .../alarm-rules/alarm-rules-table-config.ts | 59 ++++-- .../alarm-rules-table.component.ts | 4 +- .../alarm-rules/alarm-rules.component.html | 157 ++++++++++++++ .../alarm-rules/alarm-rules.component.ts | 199 ++++++++++++++++++ .../home/pages/alarm/alarm-routing.module.ts | 87 +++++++- .../alarm/alarm-rules-tabs.component.html | 30 +++ .../pages/alarm/alarm-rules-tabs.component.ts | 49 +++++ .../modules/home/pages/alarm/alarm.module.ts | 5 +- .../assets/locale/locale.constant-en_US.json | 1 + 10 files changed, 572 insertions(+), 24 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.ts diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule.module.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule.module.ts index 52d91188bc..f7adf5dab0 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule.module.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule.module.ts @@ -53,6 +53,7 @@ import { AlarmRuleTableHeaderComponent } from "@home/components/alarm-rules/alar import { AlarmRuleFilterPredicateNoDataValueComponent } from "@home/components/alarm-rules/filter/alarm-rule-filter-predicate-no-data-value.component"; +import { AlarmRulesComponent } from '@home/components/alarm-rules/alarm-rules.component'; @NgModule({ declarations: [ @@ -73,7 +74,8 @@ import { AlarmRuleDetailsDialogComponent, AlarmRuleFilterConfigComponent, AlarmRuleTableHeaderComponent, - AlarmRuleFilterPredicateNoDataValueComponent + AlarmRuleFilterPredicateNoDataValueComponent, + AlarmRulesComponent ], imports: [ CommonModule, @@ -83,6 +85,7 @@ import { ], exports: [ AlarmRuleDialogComponent, + AlarmRulesComponent ] }) export class AlarmRuleModule { } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts index b4082fb75c..b0ac04aacb 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts @@ -21,7 +21,7 @@ import { EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; import { TranslateService } from '@ngx-translate/core'; import { Direction } from '@shared/models/page/sort-order'; import { MatDialog } from '@angular/material/dialog'; @@ -67,6 +67,10 @@ import { CalculatedFieldScriptTestDialogComponent, CalculatedFieldTestScriptDialogData } from "@home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component"; +import { AlarmRulesTabsComponent } from '@home/pages/alarm/alarm-rules-tabs.component'; +import { Router } from '@angular/router'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { AlarmRulesComponent } from '@home/components/alarm-rules/alarm-rules.component'; type AlarmRuleTableEntity = CalculatedField | CalculatedFieldInfo; @@ -93,27 +97,26 @@ export class AlarmRulesTableConfig extends EntityTableConfig { - this.editCalculatedField($event, model); - return true; - }; } this.tableTitle = this.pageMode ? '' : this.translate.instant('alarm-rule.alarm-rules'); - this.detailsPanelEnabled = false; + this.detailsPanelEnabled = this.pageMode; + this.entityResources = entityTypeResources.get(EntityType.CALCULATED_FIELD); this.entityType = EntityType.CALCULATED_FIELD; this.entityTranslations = { type: 'alarm-rule.alarm-rule', typePlural: 'alarm-rule.alarm-rules', list: 'alarm-rule.list', add: 'action.add', + details: 'alarm-rule.details', noEntities: 'alarm-rule.no-found', search: 'action.search', selectedEntities: 'alarm-rule.selected-fields' @@ -121,11 +124,17 @@ export class AlarmRulesTableConfig extends EntityTableConfig this.fetchCalculatedFields(pageLink); this.addEntity = this.getCalculatedAlarmDialog.bind(this); + this.loadEntity = id => this.calculatedFieldsService.getCalculatedFieldById(id.id); + this.saveEntity = (alarmRule) => this.calculatedFieldsService.saveCalculatedField(alarmRule); + this.deleteEntityTitle = (field) => this.translate.instant('alarm-rule.delete-title', {title: field.name}); this.deleteEntityContent = () => this.translate.instant('alarm-rule.delete-text'); this.deleteEntitiesTitle = count => this.translate.instant('alarm-rule.delete-multiple-title', {count}); this.deleteEntitiesContent = () => this.translate.instant('alarm-rule.delete-multiple-text'); this.deleteEntity = id => this.calculatedFieldsService.deleteCalculatedField(id.id); + + this.onEntityAction = action => this.onCFAction(action); + this.addActionDescriptors = [ { name: this.translate.instant('alarm-rule.create'), @@ -186,14 +195,18 @@ export class AlarmRulesTableConfig extends EntityTableConfig true, iconFunction: ({ debugSettings }) => this.entityDebugSettingsService.isDebugActive(debugSettings?.allEnabledUntil) || debugSettings?.failuresEnabled ? 'mdi:bug' : 'mdi:bug-outline', onAction: ($event, entity) => this.onOpenDebugConfig($event, entity), - }, - { - name: this.translate.instant('action.edit'), - icon: 'edit', - isEnabled: () => true, - onAction: ($event, entity) => this.editCalculatedField($event, entity), } ); + if (!this.pageMode) { + this.cellActionDescriptors.push( + { + name: this.translate.instant('action.edit'), + icon: 'edit', + isEnabled: () => true, + onAction: ($event, entity) => this.editCalculatedField($event, entity), + } + ) + } } fetchCalculatedFields(pageLink: PageLink): Observable> { @@ -384,4 +397,22 @@ export class AlarmRulesTableConfig extends EntityTableConfig): boolean { + switch (action.action) { + case 'open': + this.openCalculatedField(action.event, action.entity); + return true; + case 'export': + this.exportAlarmRule(action.event, action.entity); + return true; + } + return false; + } } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table.component.ts index 7f4c0b4ee7..aaddf94aac 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table.component.ts @@ -36,7 +36,7 @@ import { EntityDebugSettingsService } from '@home/components/entity/debug/entity import { DatePipe } from '@angular/common'; import { AlarmRulesTableConfig } from "@home/components/alarm-rules/alarm-rules-table-config"; import { UtilsService } from "@core/services/utils.service"; -import { ActivatedRoute } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; @Component({ selector: 'tb-alarm-rules-table', @@ -70,6 +70,7 @@ export class AlarmRulesTableComponent { private utilsService: UtilsService, private destroyRef: DestroyRef, private route: ActivatedRoute, + private router: Router ) { this.pageMode = !!this.route.snapshot.data.isPage; effect(() => { @@ -88,6 +89,7 @@ export class AlarmRulesTableComponent { this.importExportService, this.entityDebugSettingsService, this.utilsService, + this.router, this.pageMode, ); this.cd.markForCheck(); diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html new file mode 100644 index 0000000000..e304a78732 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html @@ -0,0 +1,157 @@ + +
+ + + +
+
+
+
+
{{ 'common.general' | translate }}
+
+ + {{ 'entity-field.title' | translate }} + + @if (entityForm.get('name').errors && entityForm.get('name').touched) { + + @if (entityForm.get('name').hasError('required')) { + {{ 'common.hint.title-required' | translate }} + } @else if (entityForm.get('name').hasError('pattern')) { + {{ 'common.hint.title-pattern' | translate }} + } @else if (entityForm.get('name').hasError('maxlength')) { + {{ 'common.hint.title-max-length' | translate }} + } + + } + + + +
+ + +
+ +
+
{{ 'calculated-fields.arguments' | translate }}
+ +
+
+
{{ 'alarm-rule.create-conditions' | translate }}
+
+ + +
+
+
+
{{ 'alarm-rule.clear-condition' | translate }}
+
+
+ + +
+ +
+
+ alarm-rule.no-clear-alarm-rule +
+
+ +
+
+
+ + + {{ 'alarm-rule.advanced-settings' | translate }} + + +
+
+ + {{ 'alarm-rule.propagate-alarm' | translate }} + +
+ @if (configFormGroup.get('propagate').value) { + + + } +
+
+ + {{ 'alarm-rule.propagate-alarm-to-owner' | translate }} + +
+
+ + {{ 'alarm-rule.propagate-alarm-to-tenant' | translate }} + +
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts new file mode 100644 index 0000000000..200cf09741 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts @@ -0,0 +1,199 @@ +/// +/// 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 { ChangeDetectorRef, Component, DestroyRef, Inject, Input } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityComponent } from '../../components/entity/entity.component'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { EntityType } from '@shared/models/entity-type.models'; +import { TranslateService } from '@ngx-translate/core'; +import { + CalculatedFieldArgument, + CalculatedFieldConfiguration, + CalculatedFieldInfo, + calculatedFieldsEntityTypeList, + CalculatedFieldType +} from '@shared/models/calculated-field.models'; +import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; +import { EntityId } from '@shared/models/id/entity-id'; +import { switchMap } from 'rxjs/operators'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { BaseData } from '@shared/models/base-data'; +import { Observable } from 'rxjs'; +import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { + CalculatedFieldsTableConfig, + CalculatedFieldsTableEntity +} from '@home/components/calculated-fields/calculated-fields-table-config'; +import { TenantId } from '@shared/models/id/tenant-id'; +import { StringItemsOption } from '@shared/components/string-items-list.component'; +import { RelationTypes } from '@shared/models/relation.models'; +import { AlarmRule, AlarmRuleConditionType, AlarmRuleExpressionType } from '@shared/models/alarm-rule.models'; + +@Component({ + selector: 'tb-alarm-rules', + templateUrl: './alarm-rules.component.html', + styleUrls: [] +}) +export class AlarmRulesComponent extends EntityComponent { + + @Input() + standalone = false; + + @Input() + entityName: string; + + readonly tenantId = getCurrentAuthUser(this.store).tenantId; + readonly ownerId = new TenantId(getCurrentAuthUser(this.store).tenantId); + readonly EntityType = EntityType; + readonly calculatedFieldsEntityTypeList = calculatedFieldsEntityTypeList; + readonly CalculatedFieldType = CalculatedFieldType; + + constructor(protected store: Store, + protected translate: TranslateService, + @Inject('entity') protected entityValue: CalculatedFieldInfo, + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig, + protected fb: FormBuilder, + protected cd: ChangeDetectorRef, + private destroyRef: DestroyRef, + private calculatedFieldsService: CalculatedFieldsService) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + additionalDebugActionConfig = { + ...this.entitiesTableConfig.additionalDebugActionConfig, + action: () => this.entitiesTableConfig.additionalDebugActionConfig.action( + { id: this.entity.id, ...this.entityFormValue() }, false, + (expression) => { + if (expression) { + this.entityForm.get('configuration').setValue({...this.entityFormValue().configuration, expression}); + this.entityForm.get('configuration').markAsDirty(); + } + }), + }; + + get entityId(): EntityId { + return this.entityForm.get('entityId').value; + } + + get entitiesTableConfig(): CalculatedFieldsTableConfig { + return this.entitiesTableConfigValue; + } + + changeEntity(entity: BaseData): void { + this.entityName = entity?.name; + } + + buildForm(entity?: CalculatedFieldInfo): FormGroup { + const form = this.fb.group({ + name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]], + entityId: [null], + type: [CalculatedFieldType.ALARM], + debugSettings: [], + configuration: this.fb.group({ + type: [CalculatedFieldType.ALARM], + arguments: this.fb.control({}, Validators.required), + propagate: [false], + propagateToOwner: [false], + propagateToTenant: [false], + propagateRelationTypes: [null], + createRules: [null, Validators.required], + clearRule: [null], + }), + }); + return form; + } + + updateForm(entity: CalculatedFieldInfo) { + const { configuration = {} as CalculatedFieldConfiguration, type = CalculatedFieldType.ALARM, debugSettings = { failuresEnabled: true, allEnabled: true }, entityId = this.entityId, ...value } = entity ?? {}; + setTimeout(() => { + this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false}); + }); + if (!entityId) { + this.entityForm.get('configuration').disable({emitEvent: false}); + } + } + + onTestScript(expression?: string): Observable { + const calculatedFieldId = this.entity?.id?.id; + if (calculatedFieldId) { + return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) + .pipe( + switchMap(event => { + const args = event?.arguments ? JSON.parse(event.arguments) : null; + return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), args, false, expression); + }), + takeUntilDestroyed(this.destroyRef) + ) + } + + return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), null, false, expression); + } + + updateFormState() { + if (this.entityForm) { + if (this.isEditValue) { + this.entityForm.enable({emitEvent: false}); + this.entityForm.get('entityId').disable({emitEvent: false}); + } else { + this.entityForm.disable({emitEvent: false}); + } + } + } + + get arguments(): Record { + return this.entityForm.get('configuration.arguments').value; + } + + get predefinedTypeValues(): StringItemsOption[] { + return RelationTypes.map(type => ({ + name: type, + value: type + })); + } + + get configFormGroup(): FormGroup { + return this.entityForm.get('configuration') as FormGroup; + } + + public removeClearAlarmRule() { + this.configFormGroup.patchValue({clearRule: null}); + this.entityForm.markAsDirty(); + } + + public addClearAlarmRule() { + const clearAlarmRule: AlarmRule = { + condition: { + type: AlarmRuleConditionType.SIMPLE, + expression: { + type: AlarmRuleExpressionType.SIMPLE + } + } + }; + this.configFormGroup.patchValue({clearRule: clearAlarmRule}); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts b/ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts index 1154fc83b6..00585ffb54 100644 --- a/ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/alarm/alarm-routing.module.ts @@ -14,14 +14,64 @@ /// limitations under the License. /// -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { DestroyRef, inject, NgModule } from '@angular/core'; +import { ActivatedRouteSnapshot, ResolveFn, Router, RouterModule, RouterStateSnapshot, Routes } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component'; import { AlarmsMode } from '@shared/models/alarm.models'; import { MenuId } from '@core/services/menu.models'; import { RouterTabsComponent } from "@home/components/router-tabs.component"; import { AlarmRulesTableComponent } from "@home/components/alarm-rules/alarm-rules-table.component"; +import { CalculatedFieldsTableComponent } from '@home/components/calculated-fields/calculated-fields-table.component'; +import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component'; +import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; +import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; +import { BreadCrumbConfig } from '@shared/components/breadcrumb'; +import { CalculatedFieldsTableConfigResolver } from '@home/pages/calculated-fields/calculated-fields-routing.module'; +import { CalculatedFieldsTableConfig } from '@home/components/calculated-fields/calculated-fields-table-config'; +import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DatePipe } from '@angular/common'; +import { ImportExportService } from '@shared/import-export/import-export.service'; +import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service'; +import { UtilsService } from '@core/services/utils.service'; +import { AlarmRulesTableConfig } from '@home/components/alarm-rules/alarm-rules-table-config'; + +export const AlarmRulesTableConfigResolver: ResolveFn = + (_route: ActivatedRouteSnapshot, + _state: RouterStateSnapshot, + calculatedFieldsService = inject(CalculatedFieldsService), + translate = inject(TranslateService), + dialog = inject(MatDialog), + store = inject(Store), + datePipe = inject(DatePipe), + destroyRef = inject(DestroyRef), + importExportService = inject(ImportExportService), + entityDebugSettingsService = inject(EntityDebugSettingsService), + utilsService = inject(UtilsService), + router = inject(Router), + ) => { + return new AlarmRulesTableConfig( + calculatedFieldsService, + translate, + dialog, + datePipe, + null, + store, + destroyRef, + null, + null, + null, + importExportService, + entityDebugSettingsService, + utilsService, + router, + true, + ); + }; const routes: Routes = [ { @@ -57,15 +107,38 @@ const routes: Routes = [ }, { path: 'alarm-rules', - component: AlarmRulesTableComponent, data: { - auth: [Authority.TENANT_ADMIN], - title: 'alarm-rule.alarm-rules', breadcrumb: { menuId: MenuId.alarm_rules }, - isPage: true, - } + }, + children: [ + { + path: '', + component: AlarmRulesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'alarm-rule.alarm-rules', + isPage: true, + } + }, + { + path: ':entityId', + component: EntityDetailsPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: entityDetailsPageBreadcrumbLabelFunction, + icon: 'tune' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN], + title: 'entity.type-calculated-fields', + }, + resolve: { + entitiesTableConfig: AlarmRulesTableConfigResolver + } + } + ] } ] } diff --git a/ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.html b/ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.html new file mode 100644 index 0000000000..7cabcbf5b8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.html @@ -0,0 +1,30 @@ + +@if (entity) { + + + +} diff --git a/ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.ts new file mode 100644 index 0000000000..c239cfbd4d --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/alarm/alarm-rules-tabs.component.ts @@ -0,0 +1,49 @@ +/// +/// Copyright © 2016-2025 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; +import { CalculatedFieldEventBody, DebugEventType, EventType } from '@shared/models/event.models'; +import type { + CalculatedFieldsTableConfig, + CalculatedFieldsTableEntity +} from '@home/components/calculated-fields/calculated-fields-table-config'; +import { debugCfActionEnabled } from '@shared/models/calculated-field.models'; + +@Component({ + selector: 'tb-alarm-rules-tabs', + templateUrl: './alarm-rules-tabs.component.html', + styleUrls: [] +}) +export class AlarmRulesTabsComponent extends EntityTabsComponent { + + readonly DebugEventType = DebugEventType; + readonly EventType = EventType; + + constructor() { + super(); + } + + get debugActionDisabled(): boolean { + return !debugCfActionEnabled(this.entity); + }; + + onDebugEventSelected(event: CalculatedFieldEventBody) { + (this.entitiesTableConfig as CalculatedFieldsTableConfig).getTestScriptDialog(this.entity, JSON.parse(event.arguments)) + .subscribe((expression) => { + }); + }; +} diff --git a/ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts b/ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts index 312259b217..500c6bf47b 100644 --- a/ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts +++ b/ui-ngx/src/app/modules/home/pages/alarm/alarm.module.ts @@ -20,9 +20,12 @@ import { SharedModule } from '@shared/shared.module'; import { HomeDialogsModule } from '../../dialogs/home-dialogs.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { AlarmRoutingModule } from '@home/pages/alarm/alarm-routing.module'; +import { AlarmRulesTabsComponent } from '@home/pages/alarm/alarm-rules-tabs.component'; @NgModule({ - declarations: [], + declarations: [ + AlarmRulesTabsComponent + ], imports: [ CommonModule, SharedModule, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 8c2ecc2890..38f72b3206 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1394,6 +1394,7 @@ "create": "Create new alarm rule", "add": "Add alarm rule", "copy": "Copy alarm rule configuration", + "details": "Alarm rule details", "no-found": "No alarm rules found", "list": "{ count, plural, =1 {One alarm rule} other {List of # alarm rules} }", "selected-fields": "{ count, plural, =1 {1 alarm rule} other {# alarm rules} } selected", From 0a58ff5e771fa1cea99dec9ddbf8ce9178ed8359 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 26 Dec 2025 14:17:19 +0200 Subject: [PATCH 32/62] UI: Improved alarm rules detail panel/page --- .../calculated-field-form.service.ts | 22 ++++++- .../alarm-rule-dialog.component.html | 33 +++------- .../alarm-rule-dialog.component.ts | 63 +++++-------------- .../alarm-rules/alarm-rules-table-config.ts | 23 ++++++- .../alarm-rules/alarm-rules.component.html | 1 + .../alarm-rules/alarm-rules.component.ts | 60 ++++++------------ .../cf-alarm-rule-condition.component.html | 4 +- .../cf-alarm-rule-condition.component.ts | 16 +++-- .../create-cf-alarm-rules.component.ts | 6 +- .../calculated-field.component.html | 11 +--- .../calculated-field.component.ts | 4 +- .../calculated-field-dialog.component.html | 1 + .../calculated-field-dialog.component.ts | 2 +- .../app/shared/models/alarm-rule.models.ts | 8 ++- 14 files changed, 109 insertions(+), 145 deletions(-) rename ui-ngx/src/app/{modules/home/components/calculated-fields => core/services}/calculated-field-form.service.ts (84%) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts b/ui-ngx/src/app/core/services/calculated-field-form.service.ts similarity index 84% rename from ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts rename to ui-ngx/src/app/core/services/calculated-field-form.service.ts index 6ef02f0cf8..cc3e495c17 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts +++ b/ui-ngx/src/app/core/services/calculated-field-form.service.ts @@ -30,7 +30,6 @@ import { isDefined } from '@core/utils'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { CalculatedFieldsTableEntity } from '@home/components/calculated-fields/calculated-fields-table-config'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { EntityType } from '@shared/models/entity-type.models'; @Injectable({ providedIn: 'root' }) export class CalculatedFieldFormService { @@ -40,13 +39,32 @@ export class CalculatedFieldFormService { buildForm(): FormGroup { return this.fb.group({ name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]], - entityId: [{type: EntityType.DEVICE_PROFILE, id: null}, Validators.required], + entityId: [null, Validators.required], type: [CalculatedFieldType.SIMPLE], debugSettings: [], configuration: this.fb.control({} as CalculatedFieldConfiguration), }); } + buildAlarmRuleForm(): FormGroup { + return this.fb.group({ + name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]], + entityId: [null, Validators.required], + type: [CalculatedFieldType.ALARM], + debugSettings: [], + configuration: this.fb.group({ + type: [CalculatedFieldType.ALARM], + arguments: this.fb.control({}, Validators.required), + propagate: [false], + propagateToOwner: [false], + propagateToTenant: [false], + propagateRelationTypes: [null], + createRules: [null, Validators.required], + clearRule: [null], + }), + }); + } + setupTypeChange(form: FormGroup, destroyRef: DestroyRef, isEditActive?: () => boolean): void { form.get('type').valueChanges.pipe( pairwise(), diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html index 869b707d98..1b116d0696 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html @@ -58,31 +58,14 @@ />
@if (!data.entityId) { -
- - - @if (fieldFormGroup.get('entityId.entityType').value) { - - } -
+ + }
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts index a84aa52693..854f7fb1a0 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts @@ -14,16 +14,15 @@ /// limitations under the License. /// -import { Component, DestroyRef, Inject, ViewChild, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, Inject, ViewEncapsulation } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@shared/components/dialog.component'; import { CalculatedField, CalculatedFieldArgument, CalculatedFieldType } from '@shared/models/calculated-field.models'; -import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; -import { AliasEntityType, EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ScriptLanguage } from '@shared/models/rule-node.models'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; @@ -39,13 +38,11 @@ import { } from "@shared/models/alarm-rule.models"; import { deepTrim } from "@core/utils"; import { combineLatest, Observable } from "rxjs"; -import { debounceTime, startWith, switchMap } from "rxjs/operators"; -import { EntityTypeSelectComponent } from "@shared/components/entity/entity-type-select.component"; -import { EntityAutocompleteComponent } from "@shared/components/entity/entity-autocomplete.component"; -import { EntityService } from "@core/http/entity.service"; +import { debounceTime, startWith } from "rxjs/operators"; import { RelationTypes } from "@shared/models/relation.models"; import { StringItemsOption } from "@shared/components/string-items-list.component"; import { BaseData } from "@shared/models/base-data"; +import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; export interface AlarmRuleDialogData { value?: CalculatedField; @@ -67,24 +64,7 @@ export interface AlarmRuleDialogData { }) export class AlarmRuleDialogComponent extends DialogComponent { - fieldFormGroup = this.fb.group({ - name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]], - type: [CalculatedFieldType.ALARM], - debugSettings: [], - entityId: this.fb.group({ - entityType: this.fb.control(EntityType.DEVICE_PROFILE, Validators.required), - id: [null as null | string, Validators.required], - }), - configuration: this.fb.group({ - arguments: this.fb.control({}, Validators.required), - propagate: [false], - propagateToOwner: [false], - propagateToTenant: [false], - propagateRelationTypes: [null], - createRules: [null, Validators.required], - clearRule: [null], - }), - }); + fieldFormGroup: FormGroup ; additionalDebugActionConfig = this.data.value?.id ? { ...this.data.additionalDebugActionConfig, @@ -105,18 +85,15 @@ export class AlarmRuleDialogComponent extends DialogComponent, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AlarmRuleDialogData, protected dialogRef: MatDialogRef, private calculatedFieldsService: CalculatedFieldsService, - private entityService: EntityService, private destroyRef: DestroyRef, - private fb: FormBuilder) { + private cfFormService: CalculatedFieldFormService) { super(store, router, dialogRef); + this.fieldFormGroup = this.cfFormService.buildAlarmRuleForm(); this.applyDialogData(); this.updateRulesValidators(); @@ -132,7 +109,7 @@ export class AlarmRuleDialogComponent extends DialogComponent { - const calculatedFieldId = this.data.value?.id?.id; - if (calculatedFieldId) { - return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) - .pipe( - switchMap(event => { - const args = event?.arguments ? JSON.parse(event.arguments) : null; - return this.data.getTestScriptDialogFn(this.fromGroupValue, expression, args, false); - }), - takeUntilDestroyed(this.destroyRef) - ) - } - return this.data.getTestScriptDialogFn(this.fromGroupValue, expression, null, false); + return this.cfFormService.testScript( + this.data.value?.id?.id, + this.fromGroupValue, + this.data.getTestScriptDialogFn, + this.destroyRef, + expression + ); } private updateRulesValidators(): void { @@ -250,5 +220,4 @@ export class AlarmRuleDialogComponent extends DialogComponent): void { this.entityName = entity.name; } - } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts index b0ac04aacb..ad718408e4 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts @@ -36,7 +36,7 @@ import { DestroyRef, Renderer2 } from '@angular/core'; import { EntityDebugSettings } from '@shared/models/entity.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; -import { catchError, filter, switchMap, tap } from 'rxjs/operators'; +import { catchError, filter, first, switchMap, tap } from 'rxjs/operators'; import { ArgumentEntityType, ArgumentType, @@ -186,7 +186,8 @@ export class AlarmRulesTableConfig extends EntityTableConfig true, - onAction: ($event, entity) => this.openDebugEventsDialog($event, entity), + onAction: ($event, entity) => + this.pageMode ? this.openDebugTab($event, entity) : this.openDebugEventsDialog($event, entity), }, { name: '', @@ -308,6 +309,22 @@ export class AlarmRulesTableConfig extends EntityTableConfig 1) { + table.entityDetailsPanel.matTabGroup.selectedIndex = 1; + } else { + table.entityDetailsPanel.matTabGroup._tabs.changes.pipe( + first() + ).subscribe(() => { + table.entityDetailsPanel.matTabGroup.selectedIndex = 1; + }) + } + } + } + private exportAlarmRule($event: Event, calculatedField: AlarmRuleTableEntity): void { $event?.stopPropagation(); this.importExportService.exportCalculatedField(calculatedField.id.id); @@ -361,7 +378,7 @@ export class AlarmRulesTableConfig extends EntityTableConfig this.updateData()); } - private getTestScriptDialog(calculatedField: AlarmRuleTableEntity, expression: string, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit = true): Observable { + private getTestScriptDialog(calculatedField: AlarmRuleTableEntity, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit = true, expression?: string): Observable { if (calculatedField.type === CalculatedFieldType.ALARM) { const resultArguments = Object.keys(calculatedField.configuration.arguments).reduce((acc, key) => { const type = calculatedField.configuration.arguments[key].refEntityKey.type; diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html index e304a78732..28695d9283 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html @@ -65,6 +65,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts index 200cf09741..fb92f6c4fd 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts @@ -14,11 +14,11 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, DestroyRef, Inject, Input } from '@angular/core'; +import { ChangeDetectorRef, Component, DestroyRef, inject, Inject, Input } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { EntityComponent } from '../../components/entity/entity.component'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { EntityComponent } from '@home/components/entity/entity.component'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { EntityType } from '@shared/models/entity-type.models'; import { TranslateService } from '@ngx-translate/core'; import { @@ -28,13 +28,9 @@ import { calculatedFieldsEntityTypeList, CalculatedFieldType } from '@shared/models/calculated-field.models'; -import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; import { EntityId } from '@shared/models/id/entity-id'; -import { switchMap } from 'rxjs/operators'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { BaseData } from '@shared/models/base-data'; import { Observable } from 'rxjs'; -import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { CalculatedFieldsTableConfig, @@ -44,6 +40,7 @@ import { TenantId } from '@shared/models/id/tenant-id'; import { StringItemsOption } from '@shared/components/string-items-list.component'; import { RelationTypes } from '@shared/models/relation.models'; import { AlarmRule, AlarmRuleConditionType, AlarmRuleExpressionType } from '@shared/models/alarm-rule.models'; +import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; @Component({ selector: 'tb-alarm-rules', @@ -58,20 +55,21 @@ export class AlarmRulesComponent extends EntityComponent, protected translate: TranslateService, @Inject('entity') protected entityValue: CalculatedFieldInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig, protected fb: FormBuilder, - protected cd: ChangeDetectorRef, - private destroyRef: DestroyRef, - private calculatedFieldsService: CalculatedFieldsService) { + protected cd: ChangeDetectorRef) { super(store, fb, entityValue, entitiesTableConfigValue, cd); } @@ -107,24 +105,8 @@ export class AlarmRulesComponent extends EntityComponent { - const calculatedFieldId = this.entity?.id?.id; - if (calculatedFieldId) { - return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) - .pipe( - switchMap(event => { - const args = event?.arguments ? JSON.parse(event.arguments) : null; - return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), args, false, expression); - }), - takeUntilDestroyed(this.destroyRef) - ) - } - - return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), null, false, expression); + return this.cfFormService.testScript( + this.entity?.id?.id, + this.entityFormValue(), + this.entitiesTableConfig.getTestScriptDialog.bind(this.entitiesTableConfig), + this.destroyRef, + expression + ); } updateFormState() { diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.html index 22fc01f9de..99ee0f9634 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+
{{ 'alarm-rule.condition' | translate }}
- - - - - - - - - -
@@ -75,6 +65,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts index 01cc53609d..65595fe9b8 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts @@ -38,7 +38,7 @@ import { CalculatedFieldsTableEntity } from '@home/components/calculated-fields/calculated-fields-table-config'; import { TenantId } from '@shared/models/id/tenant-id'; -import { CalculatedFieldFormService } from '@home/components/calculated-fields/calculated-field-form.service'; +import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; @Component({ selector: 'tb-calculated-field', @@ -55,8 +55,8 @@ export class CalculatedFieldComponent extends EntityComponent 20;' -export type AlarmRuleTestScriptFn = (calculatedField: CalculatedField, expression: string, argumentsObj?: Record, closeAllOnSave?: boolean) => Observable; +export type AlarmRuleTestScriptFn = (calculatedField: CalculatedField, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit?: boolean, expression?: string) => Observable; export function checkPredicates(predicates: any[], validSet: Set): boolean { for (const predicate of predicates) { From 44f2b44d1af328a5b18f58be0a0f0bcc61a0be5d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 26 Dec 2025 16:24:07 +0200 Subject: [PATCH 33/62] UI: Fixed not apply contidition in alarm rules and fixed style --- .../home/components/alarm-rules/cf-alarm-rule.component.html | 2 +- .../components/alarm-rules/create-cf-alarm-rules.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.html index a7ccc25fe6..f48819da74 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.ts index 15d9858817..d5ec210f34 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.ts @@ -114,7 +114,7 @@ export class CreateCfAlarmRulesComponent implements ControlValueAccessor, Valida } createAlarmRulesControls.push(this.fb.group({ severity: [severity, Validators.required], - alarmRule: {value: [createAlarmRule, Validators.required], disabled: this.disabled} + alarmRule: [{value: createAlarmRule, disabled: this.disabled}, Validators.required] })); }); } From 9823c780761ca4b0b14120d9526f84199278b183 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 26 Dec 2025 17:04:37 +0200 Subject: [PATCH 34/62] UI: Fixed show label in select cf entity --- .../alarm-rules/alarm-rule-dialog.component.html | 1 + .../components/alarm-rules/alarm-rules.component.html | 1 + .../calculated-fields/calculated-field.component.html | 1 + .../dialog/calculated-field-dialog.component.html | 1 + .../components/entity/entity-select.component.html | 1 + .../components/entity/entity-select.component.ts | 3 +++ .../components/entity/entity-type-select.component.ts | 10 +++++++++- 7 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html index 1b116d0696..ed936cd60e 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html @@ -61,6 +61,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html index 7cea16c636..7d4b4b7920 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html @@ -66,6 +66,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html index 78df90701f..293dacdca6 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html @@ -59,6 +59,7 @@ >(); diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts index fcfe158da0..dcabc49906 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts @@ -52,8 +52,16 @@ export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, @coerceBoolean() showLabel: boolean; + private labelValue = this.translate.instant('entity.type'); + + get label(): string { + return this.labelValue; + } + @Input() - label = this.translate.instant('entity.type'); + set label(value: string) { + this.labelValue = value ?? this.translate.instant('entity.type'); + } @Input() @coerceBoolean() From c4ce25b6bfd353d7dc7f80a814e52b40646b8b68 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 26 Dec 2025 17:34:53 +0200 Subject: [PATCH 35/62] UI: Disabled change CF type when import --- .../calculated-fields/calculated-fields-table-config.ts | 5 +++-- .../components/dialog/calculated-field-dialog.component.ts | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts index e5e4063c21..e32bdc1188 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts @@ -253,7 +253,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { + private getCalculatedFieldDialog(value?: CalculatedFieldsTableEntity, buttonTitle = 'action.add', isDirty = false, disabledSelectType = false): Observable { const entityId = this.entityId || value?.entityId; const entityName = this.entityName || (value as CalculatedFieldInfo)?.entityName; return this.dialog.open(CalculatedFieldDialogComponent, { @@ -269,6 +269,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig this.getCalculatedFieldDialog(this.updateImportedCalculatedField(calculatedField), 'action.add', true)), + switchMap(calculatedField => this.getCalculatedFieldDialog(this.updateImportedCalculatedField(calculatedField), 'action.add', true, true)), filter(Boolean), switchMap(calculatedField => this.calculatedFieldsService.saveCalculatedField(calculatedField)), filter(Boolean), diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts index 28a028eacc..ee38fb2a2d 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts @@ -50,6 +50,7 @@ export interface CalculatedFieldDialogData { additionalDebugActionConfig: AdditionalDebugActionConfig<(calculatedField: CalculatedField) => void>; getTestScriptDialogFn: CalculatedFieldTestScriptFn; isDirty?: boolean; + disabledSelectType?: boolean; } @Component({ @@ -106,10 +107,14 @@ export class CalculatedFieldDialogComponent extends DialogComponent Date: Fri, 26 Dec 2025 17:42:16 +0200 Subject: [PATCH 36/62] UI: Time series data aggregation CF remove 'Count unique' aggregation function --- .../metrics/calculated-field-metrics-panel.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts index 3d5a53ea4e..811f7b87fa 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts @@ -74,7 +74,7 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit { entityFilter: EntityFilter; - readonly AggFunctions = Object.values(AggFunction) as AggFunction[]; + AggFunctions = Object.values(AggFunction) as AggFunction[]; readonly AggFunctionTranslations = AggFunctionTranslations; readonly ScriptLanguage = ScriptLanguage; readonly AggInputType = AggInputType; @@ -103,6 +103,10 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit { this.validateInputKey(); this.functionArgs = ['ctx', ...this.arguments]; + + if (this.simpleMode) { + this.AggFunctions = this.AggFunctions.filter(aggFunc => aggFunc !== AggFunction.COUNT_UNIQUE); + } } saveMetric(): void { From 1840401b29824aa16ef82e3f50b46d673e7d6605 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 26 Dec 2025 17:52:57 +0200 Subject: [PATCH 37/62] UI: Add show title in CF page and minor bug show required --- .../calculated-fields/calculated-fields-table-config.ts | 2 +- ...lculated-field-geofencing-zone-groups-panel.component.html | 4 +++- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts index e32bdc1188..e09949d2c6 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts @@ -111,7 +111,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig
- {{ 'calculated-fields.entity-zone-relationship' | translate }} + + {{ 'calculated-fields.entity-zone-relationship' | translate }} +
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 38f72b3206..259008791e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1183,7 +1183,7 @@ "perimeter-attribute-key": "Perimeter attribute key", "perimeter-attribute-key-required": "Perimeter attribute key is required.", "perimeter-attribute-key-pattern": "Perimeter attribute key is invalid.", - "entity-zone-relationship": "Path from Entity to Zones *", + "entity-zone-relationship": "Path from Entity to Zones", "direction": "Relation direction", "direction-from": "From entity to zone", "direction-to": "From zone to entity", From 3a647f00379a3fc398d2de5d99ab49652dfea703 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 26 Dec 2025 18:56:17 +0200 Subject: [PATCH 38/62] UI: Optimize watchKeyChange in CF arguments --- .../calculated-field-argument-panel.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.ts index 38855ec79e..51d14c9478 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.ts @@ -259,7 +259,11 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI }; } if (!onInit) { - this.argumentFormGroup.get('refEntityKey').get('key').setValue(''); + this.argumentFormGroup.get('refEntityKey').get('key').setValue('', {emitEvents: !this.watchKeyChange}); + if (this.watchKeyChange && this.argumentFormGroup.get('argumentName').pristine) { + this.argumentFormGroup.get('argumentName').markAsUntouched({emitEvent: false}); + this.argumentFormGroup.get('argumentName').setValue('', {emitEvent: false}); + } } else if (this.predefinedEntityFilter) { entityFilter = this.predefinedEntityFilter; } From 595b52d7b279d0f754dae69ea19024865d0deb7d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 26 Dec 2025 19:01:46 +0200 Subject: [PATCH 39/62] UI: Fixed cf argument validation --- .../calculated-field-arguments-table.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts index 983609dfa7..a05326699d 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts @@ -154,7 +154,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces validate(): ValidationErrors | null { this.updateErrorText(); - return this.errorText ? { argumentsFormArray: false } : null; + return this.errorText || !this.argumentsFormArray.controls.length ? { argumentsFormArray: false } : null; } setDisabledState(isDisabled: boolean): void { From bd4a450326200a4e1e5b1b1f57fb402075a094e1 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 29 Dec 2025 11:23:51 +0200 Subject: [PATCH 40/62] Use 3.8-stable tb-gateway image --- application/src/main/resources/thingsboard.yml | 2 +- .../server/controller/DeviceConnectivityControllerTest.java | 2 +- .../server/dao/device/DeviceConnectivityServiceImpl.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 787111c28e..63c461dd7c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1467,7 +1467,7 @@ device: pem_cert_file: "${DEVICE_CONNECTIVITY_COAPS_CA_ROOT_CERT:cafile.pem}" gateway: # The docker tag for thingsboard/tb-gateway image used in docker-compose file for gateway launch - image_version: "${DEVICE_CONNECTIVITY_GATEWAY_IMAGE_VERSION:3.7-stable}" + image_version: "${DEVICE_CONNECTIVITY_GATEWAY_IMAGE_VERSION:3.8-stable}" # Edges parameters edges: diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 724b1c622f..2213bb731c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -95,7 +95,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { private DeviceProfileId mqttDeviceProfileId; private DeviceProfileId coapDeviceProfileId; - @Value("${device.connectivity.gateway.image_version:3.7-stable}") + @Value("${device.connectivity.gateway.image_version:3.8-stable}") private String gatewayImageVersion; @Before diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java index 6f22e95c87..e4f2796d3b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java @@ -85,7 +85,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService private String mqttsPemCertFile; @Value("${device.connectivity.coaps.pem_cert_file:}") private String coapsPemCertFile; - @Value("${device.connectivity.gateway.image_version:3.7-stable}") + @Value("${device.connectivity.gateway.image_version:3.8-stable}") private String gatewayImageVersion; @Override From 00ab73f114d1bb042f4e4913d4d680354aec7db7 Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Mon, 29 Dec 2025 11:34:06 +0200 Subject: [PATCH 41/62] passed ctx as param to updateEntry --- ...CalculatedFieldEntityMessageProcessor.java | 15 +++++---- ...tractCalculatedFieldProcessingService.java | 6 ++-- .../service/cf/ctx/state/ArgumentEntry.java | 2 +- .../ctx/state/BaseCalculatedFieldState.java | 8 ++--- .../ctx/state/SingleValueArgumentEntry.java | 2 +- .../cf/ctx/state/TsRollingArgumentEntry.java | 2 +- .../RelatedEntitiesArgumentEntry.java | 5 +-- .../EntityAggregationArgumentEntry.java | 28 ++++++---------- ...EntityAggregationCalculatedFieldState.java | 9 ----- .../alarm/AlarmCalculatedFieldState.java | 6 ++-- .../geofencing/GeofencingArgumentEntry.java | 3 +- .../propagation/PropagationArgumentEntry.java | 3 +- .../utils/CalculatedFieldArgumentUtils.java | 4 +-- .../GeofencingValueArgumentEntryTest.java | 25 +++++++++----- .../state/PropagationArgumentEntryTest.java | 33 +++++++++++-------- .../RelatedEntitiesArgumentEntryTest.java | 15 ++++++--- .../state/SingleValueArgumentEntryTest.java | 23 ++++++++----- .../ctx/state/TsRollingArgumentEntryTest.java | 17 +++++++--- 18 files changed, 114 insertions(+), 92 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java index d2ab4c85ac..a24c5fb732 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java @@ -572,24 +572,24 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM } private Map mapToArguments(CalculatedFieldCtx ctx, List data) { - return mapToArguments(entityId, ctx.getMainEntityArguments(), Collections.emptyMap(), data); + return mapToArguments(entityId, ctx, ctx.getMainEntityArguments(), Collections.emptyMap(), data); } private Map mapToArguments(CalculatedFieldCtx ctx, EntityId entityId, List data) { - return mapToArguments(entityId, ctx.getLinkedAndDynamicArgs(entityId), ctx.getRelatedEntityArguments(), data); + return mapToArguments(entityId, ctx, ctx.getLinkedAndDynamicArgs(entityId), ctx.getRelatedEntityArguments(), data); } - private Map mapToArguments(EntityId originator, Map> args, Map> relatedEntityArgs, List data) { + private Map mapToArguments(EntityId originator, CalculatedFieldCtx ctx, Map> args, Map> relatedEntityArgs, List data) { Map arguments = new HashMap<>(); if (!relatedEntityArgs.isEmpty() || !args.isEmpty()) { for (TsKvProto item : data) { ReferencedEntityKey key = new ReferencedEntityKey(item.getKv().getKey(), ArgumentType.TS_LATEST, null); SingleValueArgumentEntry relatedArgIncoming = new SingleValueArgumentEntry(originator, item); - mapLatest(relatedArgIncoming, relatedEntityArgs.get(key), arguments); + mapLatest(ctx, relatedArgIncoming, relatedEntityArgs.get(key), arguments); SingleValueArgumentEntry incoming = new SingleValueArgumentEntry(item); - mapLatest(incoming, args.get(key), arguments); + mapLatest(ctx, incoming, args.get(key), arguments); key = new ReferencedEntityKey(item.getKv().getKey(), ArgumentType.TS_ROLLING, null); mapRolling(item, args.get(key), arguments); @@ -598,7 +598,8 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM return arguments; } - private void mapLatest(SingleValueArgumentEntry incoming, + private void mapLatest(CalculatedFieldCtx ctx, + SingleValueArgumentEntry incoming, Set argNames, Map arguments) { if (argNames != null) { @@ -606,7 +607,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM if (existing == null) { return incoming; } - existing.updateEntry(incoming); + existing.updateEntry(incoming, ctx); return existing; })); } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java b/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java index cd88c93a71..ec645085e6 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java @@ -233,7 +233,7 @@ public abstract class AbstractCalculatedFieldProcessingService { return config.getArguments().entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, - entry -> fetchTimeSeries(ctx, entityId, entry.getValue(), config.getInterval(), ts) + entry -> fetchTimeSeries(ctx.getTenantId(), entityId, entry.getValue(), config.getInterval(), ts) )); } @@ -341,11 +341,11 @@ public abstract class AbstractCalculatedFieldProcessingService { return resolveArgumentValue(argKey, argumentEntryFut); } - private ListenableFuture fetchTimeSeries(CalculatedFieldCtx ctx, EntityId entityId, Argument argument, AggInterval interval, long queryEndTs) { + private ListenableFuture fetchTimeSeries(TenantId tenantId, EntityId entityId, Argument argument, AggInterval interval, long queryEndTs) { long intervalStartTs = interval.getCurrentIntervalStartTs(); long intervalEndTs = interval.getCurrentIntervalEndTs(); ReadTsKvQuery query = new BaseReadTsKvQuery(argument.getRefEntityKey().getKey(), intervalStartTs, queryEndTs, 0, 1, Aggregation.NONE); - return fetchTimeSeriesInternal(ctx.getTenantId(), entityId, query, timeSeries -> transformAggregationArgument(timeSeries, intervalStartTs, intervalEndTs, ctx)); + return fetchTimeSeriesInternal(tenantId, entityId, query, timeSeries -> transformAggregationArgument(timeSeries, intervalStartTs, intervalEndTs)); } private ListenableFuture fetchTsRolling(TenantId tenantId, EntityId entityId, Argument argument, long queryEndTs) { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntry.java index dc23ffa979..fc4f8ce365 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntry.java @@ -52,7 +52,7 @@ public interface ArgumentEntry { Object getValue(); - boolean updateEntry(ArgumentEntry entry); + boolean updateEntry(ArgumentEntry entry, CalculatedFieldCtx ctx); boolean isEmpty(); 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 09ca35cc4b..0f648600a5 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 @@ -86,13 +86,13 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState, validateNewEntry(key, newEntry); if (existingEntry instanceof RelatedEntitiesArgumentEntry || existingEntry instanceof EntityAggregationArgumentEntry) { - updateEntry(existingEntry, newEntry); + updateEntry(existingEntry, newEntry, ctx); } else { arguments.put(key, newEntry); } entryUpdated = true; } else { - entryUpdated = updateEntry(existingEntry, newEntry); + entryUpdated = updateEntry(existingEntry, newEntry, ctx); } if (entryUpdated) { @@ -111,8 +111,8 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState, return updatedArguments; } - protected boolean updateEntry(ArgumentEntry existingEntry, ArgumentEntry newEntry) { - return existingEntry.updateEntry(newEntry); + protected boolean updateEntry(ArgumentEntry existingEntry, ArgumentEntry newEntry, CalculatedFieldCtx ctx) { + return existingEntry.updateEntry(newEntry, ctx); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SingleValueArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SingleValueArgumentEntry.java index 4d0c4d7724..028c3c429b 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SingleValueArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SingleValueArgumentEntry.java @@ -159,7 +159,7 @@ public class SingleValueArgumentEntry implements ArgumentEntry { } @Override - public boolean updateEntry(ArgumentEntry entry) { + public boolean updateEntry(ArgumentEntry entry, CalculatedFieldCtx ctx) { if (entry instanceof SingleValueArgumentEntry singleValueEntry) { if (singleValueEntry.getTs() < this.ts) { return false; diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/TsRollingArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/TsRollingArgumentEntry.java index 8cdc9ddcf9..8abddb3d4a 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/TsRollingArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/TsRollingArgumentEntry.java @@ -100,7 +100,7 @@ public class TsRollingArgumentEntry implements ArgumentEntry, HasLatestTs { } @Override - public boolean updateEntry(ArgumentEntry entry) { + public boolean updateEntry(ArgumentEntry entry, CalculatedFieldCtx ctx) { if (entry instanceof TsRollingArgumentEntry tsRollingEntry) { updateTsRollingEntry(tsRollingEntry); } else if (entry instanceof SingleValueArgumentEntry singleValueEntry) { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesArgumentEntry.java index 219cf471ed..0a5c850e4f 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesArgumentEntry.java @@ -23,6 +23,7 @@ import org.thingsboard.script.api.tbel.TbelCfSingleValueArg; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntryType; +import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; import org.thingsboard.server.service.cf.ctx.state.HasLatestTs; import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry; @@ -63,7 +64,7 @@ public class RelatedEntitiesArgumentEntry implements ArgumentEntry, HasLatestTs } @Override - public boolean updateEntry(ArgumentEntry entry) { + public boolean updateEntry(ArgumentEntry entry, CalculatedFieldCtx ctx) { if (entry instanceof RelatedEntitiesArgumentEntry relatedEntitiesArgumentEntry) { entityInputs.putAll(relatedEntitiesArgumentEntry.entityInputs); return true; @@ -74,7 +75,7 @@ public class RelatedEntitiesArgumentEntry implements ArgumentEntry, HasLatestTs } ArgumentEntry argumentEntry = entityInputs.get(singleValueArgumentEntry.getEntityId()); if (argumentEntry != null) { - argumentEntry.updateEntry(singleValueArgumentEntry); + argumentEntry.updateEntry(singleValueArgumentEntry, ctx); } else { entityInputs.put(singleValueArgumentEntry.getEntityId(), singleValueArgumentEntry); } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java index ef34b6ae8e..c0a0603390 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java @@ -39,18 +39,10 @@ public class EntityAggregationArgumentEntry implements ArgumentEntry { private boolean forceResetPrevious; - private AggInterval interval; - private long watermarkDuration; - public EntityAggregationArgumentEntry(Map aggIntervals) { this.aggIntervals = aggIntervals; } - public EntityAggregationArgumentEntry(Map aggIntervals, CalculatedFieldCtx ctx) { - this(aggIntervals); - setCtx(ctx); - } - @Override public ArgumentEntryType getType() { return ArgumentEntryType.ENTITY_AGGREGATION; @@ -61,15 +53,8 @@ public class EntityAggregationArgumentEntry implements ArgumentEntry { return aggIntervals; } - public void setCtx(CalculatedFieldCtx ctx) { - var configuration = (EntityAggregationCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration(); - interval = configuration.getInterval(); - Watermark watermark = configuration.getWatermark(); - watermarkDuration = watermark == null ? 0 : TimeUnit.SECONDS.toMillis(watermark.getDuration()); - } - @Override - public boolean updateEntry(ArgumentEntry entry) { + public boolean updateEntry(ArgumentEntry entry, CalculatedFieldCtx ctx) { if (entry instanceof EntityAggregationArgumentEntry entityAggEntry) { aggIntervals.putAll(entityAggEntry.getAggIntervals()); return true; @@ -79,7 +64,7 @@ public class EntityAggregationArgumentEntry implements ArgumentEntry { if (updateExistingIntervals(singleValueArgEntry, entryTs, now)) { return true; } - return createNewInterval(entryTs, now); + return createNewInterval(entryTs, now, ctx); } return false; } @@ -104,7 +89,14 @@ public class EntityAggregationArgumentEntry implements ArgumentEntry { return updated; } - private boolean createNewInterval(long entryTs, long now) { + private boolean createNewInterval(long entryTs, long now, CalculatedFieldCtx ctx) { + if (!(ctx.getCalculatedField().getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration config)) { + return false; + } + AggInterval interval = config.getInterval(); + Watermark watermark = config.getWatermark(); + long watermarkDuration = watermark == null ? 0 : TimeUnit.SECONDS.toMillis(watermark.getDuration()); + ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(entryTs), interval.getZoneId()); long startTs = interval.getDateTimeIntervalStartTs(zdt); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java index 59335717fb..99bddc374c 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java @@ -82,15 +82,6 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt interval = configuration.getInterval(); metrics = configuration.getMetrics(); produceIntermediateResult = configuration.isProduceIntermediateResult(); - setCtxToArguments(); - } - - private void setCtxToArguments() { - arguments.values().forEach(argument -> { - if (argument instanceof EntityAggregationArgumentEntry entityAggArgument) { - entityAggArgument.setCtx(ctx); - } - }); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java index 4f2cbede09..63b7cc8eb1 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java @@ -224,17 +224,17 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState { } @Override - protected boolean updateEntry(ArgumentEntry existingArgumentEntry, ArgumentEntry newArgumentEntry) { + protected boolean updateEntry(ArgumentEntry existingArgumentEntry, ArgumentEntry newArgumentEntry, CalculatedFieldCtx ctx) { if (!(existingArgumentEntry instanceof SingleValueArgumentEntry existingEntry) || !(newArgumentEntry instanceof SingleValueArgumentEntry newEntry)) { - return super.updateEntry(existingArgumentEntry, newArgumentEntry); + return super.updateEntry(existingArgumentEntry, newArgumentEntry, ctx); } if (newEntry.getTs() < existingEntry.getTs()) { if (existingEntry.isDefaultValue()) { existingEntry.setTs(newEntry.getTs()); } } - return super.updateEntry(existingEntry, newEntry); + return super.updateEntry(existingEntry, newEntry, ctx); } public void processAlarmAction(Alarm alarm, ActionType action) { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingArgumentEntry.java index 01c7119993..a3305ea52d 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingArgumentEntry.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntryType; +import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; import org.thingsboard.server.service.cf.ctx.state.HasLatestTs; import java.util.Map; @@ -68,7 +69,7 @@ public class GeofencingArgumentEntry implements ArgumentEntry, HasLatestTs { } @Override - public boolean updateEntry(ArgumentEntry entry) { + public boolean updateEntry(ArgumentEntry entry, CalculatedFieldCtx ctx) { if (!(entry instanceof GeofencingArgumentEntry geofencingArgumentEntry)) { throw new IllegalArgumentException("Unsupported argument entry type for geofencing argument entry: " + entry.getType()); } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java index 8536c0f65f..de04d0e817 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java @@ -21,6 +21,7 @@ import org.thingsboard.script.api.tbel.TbelCfPropagationArg; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntryType; +import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; import java.util.ArrayList; import java.util.Collection; @@ -59,7 +60,7 @@ public class PropagationArgumentEntry implements ArgumentEntry { } @Override - public boolean updateEntry(ArgumentEntry entry) { + public boolean updateEntry(ArgumentEntry entry, CalculatedFieldCtx ctx) { if (!(entry instanceof PropagationArgumentEntry updated)) { throw new IllegalArgumentException("Unsupported argument entry type for propagation argument entry: " + entry.getType()); } diff --git a/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java b/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java index d6f08cbc69..7e0701cd2d 100644 --- a/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java @@ -75,7 +75,7 @@ public class CalculatedFieldArgumentUtils { return new SingleValueArgumentEntry(); } - public static ArgumentEntry transformAggregationArgument(List timeSeries, long startIntervalTs, long endIntervalTs, CalculatedFieldCtx ctx) { + public static ArgumentEntry transformAggregationArgument(List timeSeries, long startIntervalTs, long endIntervalTs) { Map aggIntervals = new HashMap<>(); AggIntervalEntry aggIntervalEntry = new AggIntervalEntry(startIntervalTs, endIntervalTs); if (timeSeries == null || timeSeries.isEmpty()) { @@ -83,7 +83,7 @@ public class CalculatedFieldArgumentUtils { } else { aggIntervals.put(aggIntervalEntry, new AggIntervalEntryStatus(System.currentTimeMillis())); } - return new EntityAggregationArgumentEntry(aggIntervals, ctx); + return new EntityAggregationArgumentEntry(aggIntervals); } private static KvEntry createDefaultKvEntry(Argument argument) { diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingValueArgumentEntryTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingValueArgumentEntryTest.java index 6da4bdc882..d274da2434 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingValueArgumentEntryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingValueArgumentEntryTest.java @@ -18,6 +18,9 @@ package org.thingsboard.server.service.cf.ctx.state; import io.hypersistence.utils.hibernate.type.json.internal.JacksonUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.geo.PerimeterDefinition; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.EntityId; @@ -33,6 +36,7 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +@ExtendWith(MockitoExtension.class) public class GeofencingValueArgumentEntryTest { private final AssetId ZONE_1_ID = new AssetId(UUID.fromString("c0e3031c-7df1-45e4-9590-cfd621a4d714")); @@ -46,6 +50,9 @@ public class GeofencingValueArgumentEntryTest { private GeofencingArgumentEntry entry; + @Mock + private CalculatedFieldCtx ctx; + @BeforeEach void setUp() { entry = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, allowedZoneAttributeKvEntry, ZONE_2_ID, restrictedZoneAttributeKvEntry)); @@ -58,14 +65,14 @@ public class GeofencingValueArgumentEntryTest { @Test void testUpdateEntryWhenSingleEntryPassed() { - assertThatThrownBy(() -> entry.updateEntry(new SingleValueArgumentEntry())) + assertThatThrownBy(() -> entry.updateEntry(new SingleValueArgumentEntry(), ctx)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Unsupported argument entry type for geofencing argument entry: SINGLE_VALUE"); } @Test void testUpdateEntryWhenRollingEntryPassed() { - assertThatThrownBy(() -> entry.updateEntry(new TsRollingArgumentEntry(5, 30000L))) + assertThatThrownBy(() -> entry.updateEntry(new TsRollingArgumentEntry(5, 30000L), ctx)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Unsupported argument entry type for geofencing argument entry: TS_ROLLING"); } @@ -74,7 +81,7 @@ public class GeofencingValueArgumentEntryTest { void testUpdateEntryWithTheSameTs() { BaseAttributeKvEntry differentValueSameTs = new BaseAttributeKvEntry(new JsonDataEntry("zone", "[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"), 363L, 156L); var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, differentValueSameTs, ZONE_2_ID, restrictedZoneAttributeKvEntry)); - assertThat(entry.updateEntry(updated)).isFalse(); + assertThat(entry.updateEntry(updated, ctx)).isFalse(); } @Test @@ -83,7 +90,7 @@ public class GeofencingValueArgumentEntryTest { BaseAttributeKvEntry differentValueNewVersionIsNull = new BaseAttributeKvEntry(new JsonDataEntry("zone", "[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"), 364L, null); var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, differentValueNewVersionIsNull, ZONE_2_ID, restrictedZoneAttributeKvEntry)); - assertThat(entry.updateEntry(updated)).isTrue(); + assertThat(entry.updateEntry(updated, ctx)).isTrue(); assertThat(entry.getValue()).isInstanceOf(Map.class); Map value = (Map) entry.getValue(); @@ -105,7 +112,7 @@ public class GeofencingValueArgumentEntryTest { BaseAttributeKvEntry differentValueNewVersionIsSet = new BaseAttributeKvEntry(new JsonDataEntry("zone", "[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"), 364L, 156L); var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, differentValueNewVersionIsSet, ZONE_2_ID, restrictedZoneAttributeKvEntry)); - assertThat(entry.updateEntry(updated)).isTrue(); + assertThat(entry.updateEntry(updated, ctx)).isTrue(); assertThat(entry.getValue()).isInstanceOf(Map.class); Map value = (Map) entry.getValue(); @@ -126,7 +133,7 @@ public class GeofencingValueArgumentEntryTest { BaseAttributeKvEntry differentValueNewVersionIsSet = new BaseAttributeKvEntry(new JsonDataEntry("zone", "[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"), 364L, 154L); var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, differentValueNewVersionIsSet, ZONE_2_ID, restrictedZoneAttributeKvEntry)); - assertThat(entry.updateEntry(updated)).isFalse(); + assertThat(entry.updateEntry(updated, ctx)).isFalse(); } @Test @@ -134,7 +141,7 @@ public class GeofencingValueArgumentEntryTest { BaseAttributeKvEntry newTsAndTheSameValue = new BaseAttributeKvEntry(allowedZoneDataEntry, 364L, 156L); var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, newTsAndTheSameValue, ZONE_2_ID, restrictedZoneAttributeKvEntry)); - assertThat(entry.updateEntry(updated)).isTrue(); + assertThat(entry.updateEntry(updated, ctx)).isTrue(); } @Test @@ -142,7 +149,7 @@ public class GeofencingValueArgumentEntryTest { BaseAttributeKvEntry oldTsAndTheSameValue = new BaseAttributeKvEntry(allowedZoneDataEntry, 362L, 156L); var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, oldTsAndTheSameValue, ZONE_2_ID, restrictedZoneAttributeKvEntry)); - assertThat(entry.updateEntry(updated)).isFalse(); + assertThat(entry.updateEntry(updated, ctx)).isFalse(); } @Test @@ -150,7 +157,7 @@ public class GeofencingValueArgumentEntryTest { final AssetId NEW_ZONE_ID = new AssetId(UUID.fromString("a3eacf1a-6af3-4e9f-87c4-502bb25c7dc3")); BaseAttributeKvEntry newZone = new BaseAttributeKvEntry(new JsonDataEntry("zone", "[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"), 364L, 156L); var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, allowedZoneAttributeKvEntry, ZONE_2_ID, restrictedZoneAttributeKvEntry, NEW_ZONE_ID, newZone)); - assertThat(entry.updateEntry(updated)).isTrue(); + assertThat(entry.updateEntry(updated, ctx)).isTrue(); } @Test diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java index 12f3e4298d..f4098dc2df 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java @@ -17,6 +17,9 @@ package org.thingsboard.server.service.cf.ctx.state; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.script.api.tbel.TbelCfArg; import org.thingsboard.script.api.tbel.TbelCfPropagationArg; import org.thingsboard.server.common.data.id.AssetId; @@ -31,6 +34,7 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +@ExtendWith(MockitoExtension.class) public class PropagationArgumentEntryTest { private final AssetId ENTITY_1_ID = new AssetId(UUID.fromString("b0a8637d-6d67-43d5-a483-c0e391afe805")); @@ -39,6 +43,9 @@ public class PropagationArgumentEntryTest { private PropagationArgumentEntry entry; + @Mock + private CalculatedFieldCtx ctx; + @BeforeEach void setUp() { List propagationEntityIds = new ArrayList<>(); @@ -68,14 +75,14 @@ public class PropagationArgumentEntryTest { @Test void testUpdateEntryWhenSingleEntryPassed() { - assertThatThrownBy(() -> entry.updateEntry(new SingleValueArgumentEntry())) + assertThatThrownBy(() -> entry.updateEntry(new SingleValueArgumentEntry(), ctx)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Unsupported argument entry type for propagation argument entry: SINGLE_VALUE"); } @Test void testUpdateEntryWhenRollingEntryPassed() { - assertThatThrownBy(() -> entry.updateEntry(new TsRollingArgumentEntry(5, 30000L))) + assertThatThrownBy(() -> entry.updateEntry(new TsRollingArgumentEntry(5, 30000L), ctx)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Unsupported argument entry type for propagation argument entry: TS_ROLLING"); } @@ -85,7 +92,7 @@ public class PropagationArgumentEntryTest { var newIds = new ArrayList(List.of(ENTITY_3_ID, ENTITY_1_ID)); var updated = new PropagationArgumentEntry(newIds); - boolean changed = entry.updateEntry(updated); + boolean changed = entry.updateEntry(updated, ctx); assertThat(changed).isTrue(); assertThat(entry.getEntityIds()).containsExactlyElementsOf(newIds); @@ -95,7 +102,7 @@ public class PropagationArgumentEntryTest { void testUpdateEntryClearsWhenNewEntryIsEmpty() { var updatedEmpty = new PropagationArgumentEntry(List.of()); - boolean changed = entry.updateEntry(updatedEmpty); + boolean changed = entry.updateEntry(updatedEmpty, ctx); assertThat(changed).isTrue(); assertThat(entry.getEntityIds()).isEmpty(); @@ -106,7 +113,7 @@ public class PropagationArgumentEntryTest { var added = new PropagationArgumentEntry(); added.setAdded(List.of(ENTITY_3_ID)); - boolean changed = entry.updateEntry(added); + boolean changed = entry.updateEntry(added, ctx); assertThat(changed).isTrue(); assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID, ENTITY_2_ID, ENTITY_3_ID); @@ -118,7 +125,7 @@ public class PropagationArgumentEntryTest { var added = new PropagationArgumentEntry(); added.setAdded(List.of(ENTITY_2_ID)); - boolean changed = entry.updateEntry(added); + boolean changed = entry.updateEntry(added, ctx); assertThat(changed).isFalse(); assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID, ENTITY_2_ID); @@ -130,7 +137,7 @@ public class PropagationArgumentEntryTest { var removed = new PropagationArgumentEntry(); removed.setRemoved(ENTITY_2_ID); - boolean changed = entry.updateEntry(removed); + boolean changed = entry.updateEntry(removed, ctx); assertThat(changed).isTrue(); assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID); @@ -142,7 +149,7 @@ public class PropagationArgumentEntryTest { var removed = new PropagationArgumentEntry(); removed.setRemoved(ENTITY_3_ID); - boolean changed = entry.updateEntry(removed); + boolean changed = entry.updateEntry(removed, ctx); assertThat(changed).isFalse(); assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID, ENTITY_2_ID); @@ -154,7 +161,7 @@ public class PropagationArgumentEntryTest { var restore = new PropagationArgumentEntry(List.of(ENTITY_1_ID, ENTITY_2_ID, ENTITY_3_ID)); restore.setIgnoreRemovedEntities(true); - boolean changed = entry.updateEntry(restore); + boolean changed = entry.updateEntry(restore, ctx); assertThat(changed).isTrue(); assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID, ENTITY_2_ID, ENTITY_3_ID); @@ -168,7 +175,7 @@ public class PropagationArgumentEntryTest { var restore = new PropagationArgumentEntry(List.of(ENTITY_1_ID)); restore.setIgnoreRemovedEntities(true); - boolean changed = entry.updateEntry(restore); + boolean changed = entry.updateEntry(restore, ctx); assertThat(changed).isFalse(); // expected no change, since we consider the removal of stale ids as no-op assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID); @@ -182,7 +189,7 @@ public class PropagationArgumentEntryTest { var restore = new PropagationArgumentEntry(List.of(ENTITY_1_ID, ENTITY_3_ID)); restore.setIgnoreRemovedEntities(true); - boolean changed = entry.updateEntry(restore); + boolean changed = entry.updateEntry(restore, ctx); assertThat(changed).isTrue(); assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID, ENTITY_3_ID); @@ -197,7 +204,7 @@ public class PropagationArgumentEntryTest { var restore = new PropagationArgumentEntry(List.of(ENTITY_1_ID, ENTITY_2_ID)); restore.setIgnoreRemovedEntities(true); - boolean changed = entry.updateEntry(restore); + boolean changed = entry.updateEntry(restore, ctx); assertThat(changed).isFalse(); assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID, ENTITY_2_ID); @@ -211,7 +218,7 @@ public class PropagationArgumentEntryTest { var restore = new PropagationArgumentEntry(List.of()); restore.setIgnoreRemovedEntities(true); - boolean changed = entry.updateEntry(restore); + boolean changed = entry.updateEntry(restore, ctx); assertThat(changed).isFalse(); // expected no change, since we consider the removal of stale ids as no-op assertThat(entry.getEntityIds()).isEmpty(); diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesArgumentEntryTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesArgumentEntryTest.java index cc60b249ac..c357e2c30b 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesArgumentEntryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesArgumentEntryTest.java @@ -17,6 +17,9 @@ package org.thingsboard.server.service.cf.ctx.state; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; @@ -30,10 +33,14 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +@ExtendWith(MockitoExtension.class) public class RelatedEntitiesArgumentEntryTest { private RelatedEntitiesArgumentEntry entry; + @Mock + private CalculatedFieldCtx ctx; + private final DeviceId device1 = new DeviceId(UUID.fromString("1984e5f4-9ff0-4187-84ae-e4438bba4c8a")); private final DeviceId device2 = new DeviceId(UUID.fromString("937fc062-1a9d-438f-aa22-55a93fc908b7")); @@ -50,7 +57,7 @@ public class RelatedEntitiesArgumentEntryTest { @Test void testUpdateEntryWhenNotAggEntryPassed() { - assertThatThrownBy(() -> entry.updateEntry(new TsRollingArgumentEntry(5, 30000L))) + assertThatThrownBy(() -> entry.updateEntry(new TsRollingArgumentEntry(5, 30000L), ctx)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Unsupported argument entry type for aggregation argument entry: " + ArgumentEntryType.TS_ROLLING); } @@ -65,7 +72,7 @@ public class RelatedEntitiesArgumentEntryTest { device4, new SingleValueArgumentEntry(device4, new BasicTsKvEntry(ts - 60, new LongDataEntry("key", 23L), 7L)) ), false); - assertThat(entry.updateEntry(relatedEntitiesArgumentEntry)).isTrue(); + assertThat(entry.updateEntry(relatedEntitiesArgumentEntry, ctx)).isTrue(); Map aggInputs = entry.getEntityInputs(); assertThat(aggInputs.size()).isEqualTo(4); @@ -79,7 +86,7 @@ public class RelatedEntitiesArgumentEntryTest { SingleValueArgumentEntry singleEntityArgumentEntry = new SingleValueArgumentEntry(device3, new BasicTsKvEntry(ts - 50, new LongDataEntry("key", 18L), 10L)); - assertThat(entry.updateEntry(singleEntityArgumentEntry)).isTrue(); + assertThat(entry.updateEntry(singleEntityArgumentEntry, ctx)).isTrue(); Map aggInputs = entry.getEntityInputs(); assertThat(aggInputs.size()).isEqualTo(3); @@ -90,7 +97,7 @@ public class RelatedEntitiesArgumentEntryTest { void testUpdateEntryWhenSingleValueArgumentEntryPassedAndEntryByIdExist() { SingleValueArgumentEntry singleEntityArgumentEntry = new SingleValueArgumentEntry(device2, new BasicTsKvEntry(ts - 50, new LongDataEntry("key", 18L), 10L)); - assertThat(entry.updateEntry(singleEntityArgumentEntry)).isTrue(); + assertThat(entry.updateEntry(singleEntityArgumentEntry, ctx)).isTrue(); Map aggInputs = entry.getEntityInputs(); assertThat(aggInputs.size()).isEqualTo(2); diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SingleValueArgumentEntryTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SingleValueArgumentEntryTest.java index 4ada355054..e2d287c778 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SingleValueArgumentEntryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SingleValueArgumentEntryTest.java @@ -17,6 +17,9 @@ package org.thingsboard.server.service.cf.ctx.state; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.script.api.tbel.TbelCfArg; import org.thingsboard.script.api.tbel.TbelCfSingleValueArg; import org.thingsboard.server.common.data.kv.JsonDataEntry; @@ -29,10 +32,14 @@ import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +@ExtendWith(MockitoExtension.class) public class SingleValueArgumentEntryTest { private SingleValueArgumentEntry entry; + @Mock + private CalculatedFieldCtx ctx; + private final long ts = System.currentTimeMillis(); @BeforeEach @@ -47,48 +54,48 @@ public class SingleValueArgumentEntryTest { @Test void testUpdateEntryWhenRollingEntryPassed() { - assertThatThrownBy(() -> entry.updateEntry(new TsRollingArgumentEntry(5, 30000L))) + assertThatThrownBy(() -> entry.updateEntry(new TsRollingArgumentEntry(5, 30000L), ctx)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Unsupported argument entry type for single value argument entry: " + ArgumentEntryType.TS_ROLLING); } @Test void testUpdateEntryWithTheSameTs() { - assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts, new LongDataEntry("key", 13L), 363L))).isFalse(); + assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts, new LongDataEntry("key", 13L), 363L), ctx)).isFalse(); } @Test void testUpdateEntryWithTheSameTsAndDifferentVersion() { - assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts, new LongDataEntry("key", 13L), 364L))).isTrue(); + assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts, new LongDataEntry("key", 13L), 364L), ctx)).isTrue(); } @Test void testUpdateEntryWhenNewVersionIsNull() { - assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts + 16, new LongDataEntry("key", 13L), null))).isTrue(); + assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts + 16, new LongDataEntry("key", 13L), null), ctx)).isTrue(); assertThat(entry.getValue()).isEqualTo(13L); assertThat(entry.getVersion()).isNull(); } @Test void testUpdateEntryWhenNewVersionIsGreaterThanCurrent() { - assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts + 18, new LongDataEntry("key", 18L), 369L))).isTrue(); + assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts + 18, new LongDataEntry("key", 18L), 369L), ctx)).isTrue(); assertThat(entry.getValue()).isEqualTo(18L); assertThat(entry.getVersion()).isEqualTo(369L); } @Test void testUpdateEntryWhenNewVersionIsLessThanCurrent() { - assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts + 18, new LongDataEntry("key", 18L), 234L))).isFalse(); + assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts + 18, new LongDataEntry("key", 18L), 234L), ctx)).isFalse(); } @Test void testUpdateEntryWhenValueWasNotChanged() { - assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts + 18, new LongDataEntry("key", 11L), 364L))).isTrue(); + assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts + 18, new LongDataEntry("key", 11L), 364L), ctx)).isTrue(); } @Test void testUpdateEntryWithOldTs() { - assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts - 10, new LongDataEntry("key", 14L), 365L))).isFalse(); + assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts - 10, new LongDataEntry("key", 14L), 365L), ctx)).isFalse(); } @Test diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/TsRollingArgumentEntryTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/TsRollingArgumentEntryTest.java index b1f8063857..94b2a7b389 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/TsRollingArgumentEntryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/TsRollingArgumentEntryTest.java @@ -17,6 +17,9 @@ package org.thingsboard.server.service.cf.ctx.state; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; @@ -26,10 +29,14 @@ import java.util.TreeMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +@ExtendWith(MockitoExtension.class) public class TsRollingArgumentEntryTest { private TsRollingArgumentEntry entry; + @Mock + private CalculatedFieldCtx ctx; + private final long ts = System.currentTimeMillis(); @BeforeEach @@ -51,7 +58,7 @@ public class TsRollingArgumentEntryTest { void testUpdateEntryWhenSingleValueEntryPassed() { SingleValueArgumentEntry newEntry = new SingleValueArgumentEntry(ts - 10, new DoubleDataEntry("key", 23.0), 123L); - assertThat(entry.updateEntry(newEntry)).isTrue(); + assertThat(entry.updateEntry(newEntry, ctx)).isTrue(); assertThat(entry.getTsRecords()).hasSize(4); assertThat(entry.getTsRecords().get(ts - 10)).isEqualTo(23.0); } @@ -64,7 +71,7 @@ public class TsRollingArgumentEntryTest { values.put(ts - 5, 1.0); newEntry.setTsRecords(values); - assertThat(entry.updateEntry(newEntry)).isTrue(); + assertThat(entry.updateEntry(newEntry, ctx)).isTrue(); assertThat(entry.getTsRecords()).hasSize(5); assertThat(entry.getTsRecords()).isEqualTo(Map.of( ts - 40, 10.0, @@ -79,7 +86,7 @@ public class TsRollingArgumentEntryTest { void testUpdateEntryWhenValueIsNotNumber() { SingleValueArgumentEntry newEntry = new SingleValueArgumentEntry(ts - 10, new StringDataEntry("key", "string"), 123L); - assertThat(entry.updateEntry(newEntry)).isTrue(); + assertThat(entry.updateEntry(newEntry, ctx)).isTrue(); assertThat(entry.getTsRecords().get(ts - 10)).isNaN(); } @@ -93,7 +100,7 @@ public class TsRollingArgumentEntryTest { newEntry.setTsRecords(values); entry = new TsRollingArgumentEntry(3, 30000L); - assertThat(entry.updateEntry(newEntry)).isTrue(); + assertThat(entry.updateEntry(newEntry, ctx)).isTrue(); assertThat(entry.getTsRecords()).hasSize(1); assertThat(entry.getTsRecords()).isEqualTo(Map.of( ts - 5, 0.0 @@ -111,7 +118,7 @@ public class TsRollingArgumentEntryTest { newEntry.setTsRecords(values); entry = new TsRollingArgumentEntry(3, 30000L); - assertThat(entry.updateEntry(newEntry)).isTrue(); + assertThat(entry.updateEntry(newEntry, ctx)).isTrue(); assertThat(entry.getTsRecords()).hasSize(3); assertThat(entry.getTsRecords()).isEqualTo(Map.of( ts - 18, 0.0, From 860195426c163f980513a367a1634deea6de3bb7 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 29 Dec 2025 12:36:57 +0200 Subject: [PATCH 42/62] UI: Fixed loading state in login pages --- .../login/pages/login/create-password.component.ts | 10 +++++----- .../modules/login/pages/login/login.component.html | 6 +++--- .../app/modules/login/pages/login/login.component.ts | 11 +++++++---- .../login/pages/login/reset-password.component.ts | 8 ++++---- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts b/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts index 0f2ec26ad3..f0a6bdd0ff 100644 --- a/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts @@ -20,6 +20,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { UserPasswordPolicy } from '@shared/models/settings.models'; import { passwordsMatchValidator, passwordStrengthValidator } from '@shared/models/password.models'; +import { finalize } from 'rxjs/operators'; @Component({ selector: 'tb-create-password', @@ -60,11 +61,10 @@ export class CreatePasswordComponent { if (this.createPassword.invalid) { this.createPassword.markAllAsTouched(); } else { - this.isLoading = true - this.authService.activate(this.activateToken, this.createPassword.get('newPassword').value, true) - .subscribe({ - error: () => {this.isLoading = false;} - }); + this.isLoading = true; + this.authService.activate(this.activateToken, this.createPassword.get('newPassword').value, true).pipe( + finalize(() => {this.isLoading = false;}) + ).subscribe(() => {}); } } } diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.html b/ui-ngx/src/app/modules/login/pages/login/login.component.html index f43ff18435..f2878bde27 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.html @@ -22,9 +22,9 @@
- + - +
@if(oauth2Clients?.length) { @@ -70,7 +70,7 @@ lock -
-
diff --git a/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts b/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts index f0a6bdd0ff..6e9be088c7 100644 --- a/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts @@ -21,13 +21,14 @@ import { ActivatedRoute } from '@angular/router'; import { UserPasswordPolicy } from '@shared/models/settings.models'; import { passwordsMatchValidator, passwordStrengthValidator } from '@shared/models/password.models'; import { finalize } from 'rxjs/operators'; +import { PageComponent } from '@shared/components/page.component'; @Component({ selector: 'tb-create-password', templateUrl: './create-password.component.html', styleUrls: ['./password.component.scss'] }) -export class CreatePasswordComponent { +export class CreatePasswordComponent extends PageComponent { passwordPolicy: UserPasswordPolicy; createPassword: FormGroup; @@ -39,7 +40,7 @@ export class CreatePasswordComponent { constructor(private route: ActivatedRoute, private authService: AuthService, private fb: FormBuilder) { - + super(); this.activateToken = this.route.snapshot.queryParams['activateToken'] || ''; this.passwordPolicy = this.route.snapshot.data['passwordPolicy']; From 4e45f82cde00dce0d5acbf6a2d0e6b02c1eb86da Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Tue, 23 Dec 2025 19:24:43 +0200 Subject: [PATCH 48/62] UI: Show create relation only for relation query type --- ...calculated-field-geofencing-zone-groups-panel.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.html index 52c0226275..e09d5b0f4e 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.html @@ -223,7 +223,7 @@
-
+
{{ 'calculated-fields.create-relation-with-matched-zones' | translate }} From 02cc1f3010224978f5ffda30f70e0fc20f766350 Mon Sep 17 00:00:00 2001 From: dshvaika Date: Tue, 30 Dec 2025 12:13:22 +0200 Subject: [PATCH 49/62] cleanup CF integration tests from duplicate doPost API calls --- .../cf/CalculatedFieldIntegrationTest.java | 131 ++++++------------ .../server/controller/AbstractWebTest.java | 2 +- 2 files changed, 46 insertions(+), 87 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java b/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java index fa30dc9025..334755cea9 100644 --- a/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java @@ -20,9 +20,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.Test; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.AttributeScope; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.EntityType; @@ -76,7 +74,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes public void testSimpleCalculatedFieldWhenAllTelemetryPresent() throws Exception { Device testDevice = createDevice("Test device", "1234567890"); postTelemetry(testDevice.getId(), "{\"temperature\":25}"); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"deviceTemperature\":40}")); + postAttributes(testDevice.getId(), AttributeScope.SERVER_SCOPE, "{\"deviceTemperature\":40}"); CalculatedField calculatedField = new CalculatedField(); calculatedField.setEntityId(testDevice.getId()); @@ -259,15 +257,15 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes @Test public void testSimpleCalculatedFieldWhenEntityIdIsProfile() throws Exception { Device testDevice = createDevice("Test device", "1234567890"); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"x\":40}")); + postAttributes(testDevice.getId(), AttributeScope.SERVER_SCOPE, "{\"x\":40}"); AssetProfile assetProfile = doPost("/api/assetProfile", createAssetProfile("Test Asset Profile"), AssetProfile.class); Asset asset1 = createAsset("Test asset 1", assetProfile.getId()); - doPost("/api/plugins/telemetry/ASSET/" + asset1.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"y\":11}")); + postAttributes(asset1.getId(), AttributeScope.SERVER_SCOPE, "{\"y\":11}"); Asset asset2 = createAsset("Test asset 2", assetProfile.getId()); - doPost("/api/plugins/telemetry/ASSET/" + asset2.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"y\":12}")); + postAttributes(asset2.getId(), AttributeScope.SERVER_SCOPE, "{\"y\":12}"); CalculatedField calculatedField = new CalculatedField(); calculatedField.setEntityId(assetProfile.getId()); @@ -316,7 +314,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes assertThat(z2.get(0).get("value").asText()).isEqualTo("52.0"); }); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"x\":25}")); + postAttributes(testDevice.getId(), AttributeScope.SERVER_SCOPE, "{\"x\":25}"); await().alias("update device telemetry -> recalculate state for all assets").atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) @@ -332,7 +330,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes assertThat(z2.get(0).get("value").asText()).isEqualTo("37.0"); }); - doPost("/api/plugins/telemetry/ASSET/" + asset1.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"y\":15}")); + postAttributes(asset1.getId(), AttributeScope.SERVER_SCOPE, "{\"y\":15}"); await().alias("update asset 1 telemetry -> recalculate state only for asset 1").atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) @@ -348,7 +346,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes assertThat(z2.get(0).get("value").asText()).isEqualTo("37.0"); }); - doPost("/api/plugins/telemetry/ASSET/" + asset2.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"y\":5}")); + postAttributes(asset2.getId(), AttributeScope.SERVER_SCOPE, "{\"y\":5}"); await().alias("update asset 2 telemetry -> recalculate state only for asset 2").atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) @@ -365,7 +363,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes }); Asset asset3 = createAsset("Test asset 3", assetProfile.getId()); - doPost("/api/plugins/telemetry/ASSET/" + asset3.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"y\":13}")); + postAttributes(asset3.getId(), AttributeScope.SERVER_SCOPE, "{\"y\":13}"); Asset finalAsset3 = asset3; await().alias("add new entity to profile -> calculate state for new entity").atMost(TIMEOUT, TimeUnit.SECONDS) @@ -377,7 +375,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes assertThat(z3.get(0).get("value").asText()).isEqualTo("38.0"); }); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"x\":20}")); + postAttributes(testDevice.getId(), AttributeScope.SERVER_SCOPE, "{\"x\":20}"); await().alias("update device telemetry -> recalculate state for all assets").atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) @@ -403,7 +401,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes asset3.setAssetProfileId(newAssetProfile.getId()); asset3 = doPost("/api/asset", asset3, Asset.class); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"x\":15}")); + postAttributes(testDevice.getId(), AttributeScope.SERVER_SCOPE, "{\"x\":15}"); Asset updatedAsset3 = asset3; await().alias("update device telemetry -> recalculate state for asset 1 and asset 2").atMost(TIMEOUT, TimeUnit.SECONDS) @@ -662,7 +660,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes assertThat(d.get("d").get(0).get("value").asText()).isEqualTo("600"); }); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"a\":10}")); + postTelemetry(testDevice.getId(), "{\"a\":10}"); await().alias("update telemetry -> save result with ts of 'a' argument").atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) @@ -674,8 +672,8 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes assertThat(keys.get("d").get(0).get("value").asText()).isEqualTo("510"); }); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"b\":20}")); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"c\":30}")); + postTelemetry(testDevice.getId(), "{\"b\":20}"); + postTelemetry(testDevice.getId(), "{\"c\":30}"); await().alias("update telemetry -> save result with latest ts of updated arguments").atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) @@ -726,7 +724,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes calculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"m\":1}")); + postTelemetry(testDevice.getId(), "{\"m\":1}"); await().alias("create CF -> ctx is initialized -> perform calculation").atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) @@ -740,7 +738,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes calculatedField.setConfiguration(config); calculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"m\":2}")); + postTelemetry(testDevice.getId(), "{\"m\":2}"); await().alias("update CF -> ctx is not initialized -> no calculation performed").atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) @@ -761,15 +759,11 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes // Restricted zone polygon (square) String restrictedPolygon = "[[50.475000, 30.510000], [50.475000, 30.512000], [50.477000, 30.512000], [50.477000, 30.510000]]"; - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, - JacksonUtil.toJsonNode("{\"allowedZone\":" + allowedPolygon + "}")).andExpect(status().isOk()); - - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, - JacksonUtil.toJsonNode("{\"restrictedZone\":" + restrictedPolygon + "}")).andExpect(status().isOk()); + postAttributes(device.getId(), AttributeScope.SERVER_SCOPE, "{\"allowedZone\":" + allowedPolygon + "}"); + postAttributes(device.getId(), AttributeScope.SERVER_SCOPE, "{\"restrictedZone\":" + restrictedPolygon + "}"); // Initial device coordinates (inside Allowed, outside Restricted) - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope", - JacksonUtil.toJsonNode("{\"latitude\":50.4730,\"longitude\":30.5050}")); + postTelemetry(device.getId(), "{\"latitude\":50.4730,\"longitude\":30.5050}"); // --- Build CF: GEOFENCING --- CalculatedField cf = new CalculatedField(); @@ -817,8 +811,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes doDelete("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/SERVER_SCOPE?keys=allowedZonesStatus,restrictedZonesStatus", String.class); // --- Update restrictedZone by 'restrictedZone' attribute update - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, - JacksonUtil.toJsonNode("{\"restrictedZone\":" + restrictedPolygon + "}")).andExpect(status().isOk()); + postAttributes(device.getId(), AttributeScope.SERVER_SCOPE, "{\"restrictedZone\":" + restrictedPolygon + "}"); // --- Assert no transition --- // --- Assert attributes updated with the same values for restrictedZones --- @@ -848,30 +841,17 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes String restrictedPolygon = "[[50.475000, 30.510000], [50.475000, 30.512000], [50.477000, 30.512000], [50.477000, 30.510000]]"; Asset allowedZoneAsset = createAsset("Allowed Zone", null); - doPost("/api/plugins/telemetry/ASSET/" + allowedZoneAsset.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, - JacksonUtil.toJsonNode("{\"zone\":" + allowedPolygon + "}")).andExpect(status().isOk()); + postAttributes(allowedZoneAsset.getId(), AttributeScope.SERVER_SCOPE, "{\"zone\":" + allowedPolygon + "}"); Asset restrictedZoneAsset = createAsset("Restricted Zone", null); - doPost("/api/plugins/telemetry/ASSET/" + restrictedZoneAsset.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, - JacksonUtil.toJsonNode("{\"zone\":" + restrictedPolygon + "}")).andExpect(status().isOk()); + postAttributes(restrictedZoneAsset.getId(), AttributeScope.SERVER_SCOPE, "{\"zone\":" + restrictedPolygon + "}"); // Relations from device to zones - EntityRelation deviceToAllowedZoneRelation = new EntityRelation(); - deviceToAllowedZoneRelation.setFrom(device.getId()); - deviceToAllowedZoneRelation.setTo(allowedZoneAsset.getId()); - deviceToAllowedZoneRelation.setType("AllowedZone"); - - EntityRelation deviceToRestrictedZoneRelation = new EntityRelation(); - deviceToRestrictedZoneRelation.setFrom(device.getId()); - deviceToRestrictedZoneRelation.setTo(restrictedZoneAsset.getId()); - deviceToRestrictedZoneRelation.setType("RestrictedZone"); - - doPost("/api/relation", deviceToAllowedZoneRelation).andExpect(status().isOk()); - doPost("/api/relation", deviceToRestrictedZoneRelation).andExpect(status().isOk()); + createEntityRelation(device.getId(), allowedZoneAsset.getId(), "AllowedZone"); + createEntityRelation(device.getId(), restrictedZoneAsset.getId(), "RestrictedZone"); // Initial device coordinates (inside Allowed, outside Restricted) - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope", - JacksonUtil.toJsonNode("{\"latitude\":50.4730,\"longitude\":30.5050}")); + postTelemetry(device.getId(), "{\"latitude\":50.4730,\"longitude\":30.5050}"); // --- Build CF: GEOFENCING --- CalculatedField cf = new CalculatedField(); @@ -922,8 +902,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes }); // --- Move the device into Restricted zone (and outside Allowed) --- - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope", - JacksonUtil.toJsonNode("{\"latitude\":50.4760,\"longitude\":30.5110}")); + postTelemetry(device.getId(), "{\"latitude\":50.4760,\"longitude\":30.5110}"); // --- Assert transition (LEFT / ENTERED) --- await().alias("transition evaluation after movement") @@ -965,19 +944,13 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes String allowedPolygonA = "[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]"; Asset allowedZoneA = createAsset("Allowed Zone A", null); - doPost("/api/plugins/telemetry/ASSET/" + allowedZoneA.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, - JacksonUtil.toJsonNode("{\"zone\":" + allowedPolygonA + "}")).andExpect(status().isOk()); + postAttributes(allowedZoneA.getId(), AttributeScope.SERVER_SCOPE, "{\"zone\":" + allowedPolygonA + "}"); // Relation from device to Allowed Zone A - EntityRelation relAllowedA = new EntityRelation(); - relAllowedA.setFrom(device.getId()); - relAllowedA.setTo(allowedZoneA.getId()); - relAllowedA.setType("AllowedZone"); - doPost("/api/relation", relAllowedA).andExpect(status().isOk()); + createEntityRelation(device.getId(), allowedZoneA.getId(), "AllowedZone"); // Initial device coordinates: INSIDE Zone A - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope", - JacksonUtil.toJsonNode("{\"latitude\":50.4730,\"longitude\":30.5050}")).andExpect(status().isOk()); + postTelemetry(device.getId(), "{\"latitude\":50.4730,\"longitude\":30.5050}"); // --- Build CF: GEOFENCING with dynamic 'allowedZones' and short scheduled refresh --- CalculatedField cf = new CalculatedField(); @@ -1024,8 +997,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes }); // --- Move device OUTSIDE Zone A (expect LEFT) --- - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope", - JacksonUtil.toJsonNode("{\"latitude\":50.4760,\"longitude\":30.5110}")).andExpect(status().isOk()); + postTelemetry(device.getId(), "{\"latitude\":50.4760,\"longitude\":30.5110}"); await().alias("outside zone A (LEFT)") .atMost(TIMEOUT, TimeUnit.SECONDS) @@ -1042,21 +1014,15 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes String allowedPolygonB = "[[50.475500, 30.510500], [50.475500, 30.511500], [50.476500, 30.511500], [50.476500, 30.510500]]"; Asset allowedZoneB = createAsset("Allowed Zone B", null); - doPost("/api/plugins/telemetry/ASSET/" + allowedZoneB.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, - JacksonUtil.toJsonNode("{\"zone\":" + allowedPolygonB + "}")).andExpect(status().isOk()); + postAttributes(allowedZoneB.getId(), AttributeScope.SERVER_SCOPE, "{\"zone\":" + allowedPolygonB + "}"); // Add a new relation - EntityRelation relAllowedB = new EntityRelation(); - relAllowedB.setFrom(device.getId()); - relAllowedB.setTo(allowedZoneB.getId()); - relAllowedB.setType("AllowedZone"); - doPost("/api/relation", relAllowedB).andExpect(status().isOk()); + createEntityRelation(device.getId(), allowedZoneB.getId(), "AllowedZone"); awaitForCalculatedFieldEntityMessageProcessorToRegisterCfStateAsReadyToRefreshDynamicArguments(device.getId(), savedCalculatedField.getId(), minAllowedScheduledUpdateIntervalInSecForCF); // --- Same coordinates as before, but now we expect INSIDE group status since a new zone is registered --- - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope", - JacksonUtil.toJsonNode("{\"latitude\":50.4760,\"longitude\":30.5110}")).andExpect(status().isOk()); + postTelemetry(device.getId(), "{\"latitude\":50.4760,\"longitude\":30.5110}"); // --- Assert dynamic refresh picks up a new relation and flips status back to INSIDE on the next telemetry update --- await().alias("dynamic refresh rebinds allowedZones") @@ -1079,14 +1045,11 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes Asset asset2 = createAsset("Propagated Asset 2", null); // Create relations FROM assets TO device - EntityRelation rel1 = new EntityRelation(asset1.getId(), device.getId(), EntityRelation.CONTAINS_TYPE); - EntityRelation rel2 = new EntityRelation(asset2.getId(), device.getId(), EntityRelation.CONTAINS_TYPE); - doPost("/api/relation", rel1).andExpect(status().isOk()); - doPost("/api/relation", rel2).andExpect(status().isOk()); + createEntityRelation(asset1.getId(), device.getId(), EntityRelation.CONTAINS_TYPE); + createEntityRelation(asset2.getId(), device.getId(), EntityRelation.CONTAINS_TYPE); // Telemetry on device - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope", - JacksonUtil.toJsonNode("{\"temperature\":12.5, \"humidity\":85}")).andExpect(status().isOk()); + postTelemetry(device.getId(), "{\"temperature\":12.5, \"humidity\":85}"); // --- Build CF: PROPAGATION with expression --- CalculatedField cf = new CalculatedField(); @@ -1136,8 +1099,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes doDelete(deleteUrl).andExpect(status().isOk()); doDelete("/api/plugins/telemetry/ASSET/" + asset1.getId() + "/SERVER_SCOPE?keys=testResult").andExpect(status().isOk()); - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope", - JacksonUtil.toJsonNode("{\"temperature\":25}")).andExpect(status().isOk()); + postTelemetry(device.getId(), "{\"temperature\":25}"); // --- Assert propagated calculation (expression applied with new temperature argument and one relation removed) --- await().alias("propagation expr mode evaluation after temperature update") @@ -1160,10 +1122,8 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes Asset asset2 = createAsset("Propagated Asset 2", null); // Create relations FROM assets TO device - EntityRelation rel1 = new EntityRelation(asset1.getId(), device.getId(), EntityRelation.CONTAINS_TYPE); - EntityRelation rel2 = new EntityRelation(asset2.getId(), device.getId(), EntityRelation.CONTAINS_TYPE); - doPost("/api/relation", rel1).andExpect(status().isOk()); - doPost("/api/relation", rel2).andExpect(status().isOk()); + createEntityRelation(asset1.getId(), device.getId(), EntityRelation.CONTAINS_TYPE); + createEntityRelation(asset2.getId(), device.getId(), EntityRelation.CONTAINS_TYPE); // Telemetry on device long ts = System.currentTimeMillis() - 300000L; @@ -1245,8 +1205,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes }); Asset asset3 = createAsset("Propagated Asset 3", null); - EntityRelation rel3 = new EntityRelation(asset3.getId(), device.getId(), EntityRelation.CONTAINS_TYPE); - doPost("/api/relation", rel3).andExpect(status().isOk()); + createEntityRelation(asset3.getId(), device.getId(), EntityRelation.CONTAINS_TYPE); // --- Assert propagated calculation (arguments-only mode after update) --- await().alias("propagation args-only to new entity after relation creation") @@ -1266,7 +1225,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes @Test public void testCalculatedFieldWhenTheSameTelemetryKeysUsed() throws Exception { Device testDevice = createDevice("Test device", "1234567890"); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"a\":5}")); + postTelemetry(testDevice.getId(), "{\"a\":5}"); CalculatedField calculatedField = new CalculatedField(); calculatedField.setEntityId(testDevice.getId()); @@ -1301,7 +1260,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes assertThat(c.get("c").get(0).get("value").asText()).isEqualTo("10"); }); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"a\":10}")); + postTelemetry(testDevice.getId(), "{\"a\":10}"); await().alias("update telemetry -> recalculate state").atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) @@ -1316,8 +1275,8 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes public void testCalculatedFieldWhenBatchOfTelemetrySent() throws Exception { Device testDevice = createDevice("Test device", "1234567890"); long now = System.currentTimeMillis(); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode(String.format("{\"ts\": %s, \"values\": {\"a\":5, \"b\":10}}", now - TimeUnit.MINUTES.toMillis(3)))); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode(String.format("{\"ts\": %s, \"values\": {\"b\":20}}", now - TimeUnit.MINUTES.toMillis(1)))); + postTelemetry(testDevice.getId(), String.format("{\"ts\": %s, \"values\": {\"a\":5, \"b\":10}}", now - TimeUnit.MINUTES.toMillis(3))); + postTelemetry(testDevice.getId(), String.format("{\"ts\": %s, \"values\": {\"b\":20}}", now - TimeUnit.MINUTES.toMillis(1))); CalculatedField calculatedField = new CalculatedField(); calculatedField.setEntityId(testDevice.getId()); @@ -1358,7 +1317,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes assertThat(result.get("avgB").get(0).get("value").asText()).isEqualTo("15.0"); }); - doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode(String.format(""" + postTelemetry(testDevice.getId(), String.format(""" [{ "ts": %s, "values": { @@ -1377,7 +1336,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes "a": 8, "b": 300 } - }]""", now - TimeUnit.MINUTES.toMillis(2), now, now - TimeUnit.MINUTES.toMillis(5)))); + }]""", now - TimeUnit.MINUTES.toMillis(2), now, now - TimeUnit.MINUTES.toMillis(5))); await().alias("update telemetry -> recalculate state").atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index b9c6e17ded..3df3cf1d20 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -1143,7 +1143,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { protected void createEntityRelation(EntityId entityIdFrom, EntityId entityIdTo, String typeRelation) throws Exception { EntityRelation relation = new EntityRelation(entityIdFrom, entityIdTo, typeRelation); - doPost("/api/relation", relation); + doPost("/api/relation", relation).andExpect(status().isOk()); } protected void deleteEntityRelation(EntityRelation entityRelation) throws Exception { From dac772b47610a87cb95f3254f61bee25ae3fe026 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Tue, 30 Dec 2025 12:15:27 +0200 Subject: [PATCH 50/62] Fix release notes generation for bugs section --- .github/release.yml | 51 ++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/.github/release.yml b/.github/release.yml index 53b2e3ae65..1dad5d274f 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -23,27 +23,19 @@ changelog: labels: - 'Major Core' - 'Major Rule Engine' - exclude: - labels: - - 'Bug' + - title: 'Major UI' labels: - 'Major UI' - exclude: - labels: - - 'Bug' + - title: 'Major Transport' labels: - 'Major Transport' - exclude: - labels: - - 'Bug' + - title: 'Major Edge' labels: - 'Major Edge' - exclude: - labels: - - 'Bug' + - title: 'Core & Rule Engine' labels: - 'Core' @@ -51,38 +43,63 @@ changelog: exclude: labels: - 'Bug' + - title: 'UI' labels: - 'UI' exclude: labels: - 'Bug' + - title: 'Transport' labels: - 'Transport' exclude: labels: - 'Bug' + - title: 'Edge' labels: - 'Edge' exclude: labels: - 'Bug' + - title: 'Bug: Core & Rule Engine' labels: - - 'Core' - - 'Rule Engine' - 'Bug' + exclude: + labels: + - 'UI' + - 'Transport' + - 'Edge' + - title: 'Bug: UI' labels: - - 'UI' - 'Bug' + exclude: + labels: + - 'Core' + - 'Rule Engine' + - 'Transport' + - 'Edge' + - title: 'Bug: Transport' labels: - - 'Transport' - 'Bug' + exclude: + labels: + - 'Core' + - 'Rule Engine' + - 'UI' + - 'Edge' + - title: 'Bug: Edge' labels: - - 'Edge' - 'Bug' + exclude: + labels: + - 'Core' + - 'Rule Engine' + - 'UI' + - 'Transport' From 3764dc061d3204eb43d759cc3e5568ff375fb587 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 30 Dec 2025 16:30:36 +0200 Subject: [PATCH 51/62] UI: Updated cf hint text and updated style selected cf type --- .../calculated-field.component.html | 18 ++++++++++--- .../calculated-field-dialog.component.html | 18 ++++++++++--- ...ntity-aggregation-component.component.html | 4 +-- ...culated-field-metrics-panel.component.html | 5 +++- .../calculated-field-output.component.html | 4 ++- ...ities-aggregation-component.component.html | 2 +- .../simple-configuration.component.html | 10 ++++++-- .../shared/models/calculated-field.models.ts | 12 ++++++--- .../assets/locale/locale.constant-en_US.json | 25 ++++++++++++++----- 9 files changed, 73 insertions(+), 25 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html index 7d4b4b7920..1150b5b2a6 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html @@ -74,13 +74,23 @@ {{ 'common.type' | translate }} + + {{ CalculatedFieldTypeTranslations.has(entityForm.get('type').value) + ? (CalculatedFieldTypeTranslations.get(entityForm.get('type').value).name | translate) + : entityForm.get('type').value }} + @for (type of fieldTypes; track type) { - {{ CalculatedFieldTypeTranslations.get(type).name | translate }} + + + {{ CalculatedFieldTypeTranslations.get(type).name | translate }} + +
+ + {{ CalculatedFieldTypeTranslations.get(type).hint | translate }} + +
}
- @if (CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint) { - {{ CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint | translate }} - }
@switch (entityForm.get('type').value) { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html index 293dacdca6..1c4a2995b5 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html @@ -69,13 +69,23 @@ {{ 'common.type' | translate }} + + {{ CalculatedFieldTypeTranslations.has(fieldFormGroup.get('type').value) + ? (CalculatedFieldTypeTranslations.get(fieldFormGroup.get('type').value).name | translate) + : fieldFormGroup.get('type').value }} + @for (type of fieldTypes; track type) { - {{ CalculatedFieldTypeTranslations.get(type).name | translate}} + + + {{ CalculatedFieldTypeTranslations.get(type).name | translate}} + +
+ + {{ CalculatedFieldTypeTranslations.get(type).hint | translate }} + +
}
- @if (CalculatedFieldTypeTranslations.get(fieldFormGroup.get('type').value).hint) { - {{ CalculatedFieldTypeTranslations.get(fieldFormGroup.get('type').value).hint | translate }} - }
@switch (fieldFormGroup.get('type').value) { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html index 19600f8085..1eb0a63361 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html @@ -17,7 +17,7 @@ -->
-
+
{{ 'calculated-fields.arguments' | translate }}
@@ -29,7 +29,7 @@ [entityName]="entityName"/>
-
+
{{ 'calculated-fields.metrics.metrics' | translate }}
@if (!simpleMode) {
-
{{ 'calculated-fields.metrics.value-source' | translate }}
+
+ {{ 'calculated-fields.metrics.value-source' | translate }} +
@for (inputType of AggInputTypes; track inputType) { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html index 5bf7788e10..c1d55dd03a 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html @@ -16,7 +16,9 @@ -->
-
{{ 'calculated-fields.output' | translate }}
+
+ {{ 'calculated-fields.output' | translate }} +
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/related-entities-aggregation-configuration/related-entities-aggregation-component.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/related-entities-aggregation-configuration/related-entities-aggregation-component.component.html index d10bf75f06..eb82ecbe70 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/related-entities-aggregation-configuration/related-entities-aggregation-component.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/related-entities-aggregation-configuration/related-entities-aggregation-component.component.html @@ -63,10 +63,10 @@ diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html index aed0ef8f06..2b1f0cfc01 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html @@ -17,7 +17,10 @@ -->
-
{{ 'calculated-fields.arguments' | translate }}
+
+ {{ 'calculated-fields.arguments' | translate }} +
-
{{ (isScript ? 'calculated-fields.type.script' : 'calculated-fields.expression') | translate }}
+
+ {{ (isScript ? 'calculated-fields.type.script' : 'calculated-fields.expression') | translate }} +
diff --git a/ui-ngx/src/app/shared/models/calculated-field.models.ts b/ui-ngx/src/app/shared/models/calculated-field.models.ts index 30f5a27686..b0c2c1a899 100644 --- a/ui-ngx/src/app/shared/models/calculated-field.models.ts +++ b/ui-ngx/src/app/shared/models/calculated-field.models.ts @@ -99,16 +99,20 @@ interface CalculatedFieldTypeTranslate { export const CalculatedFieldTypeTranslations = new Map( [ [CalculatedFieldType.SIMPLE, { - name: 'calculated-fields.type.simple' + name: 'calculated-fields.type.simple', + hint: 'calculated-fields.type.simple-hint', }], [CalculatedFieldType.SCRIPT, { - name: 'calculated-fields.type.script' + name: 'calculated-fields.type.script', + hint: 'calculated-fields.type.script-hint', }], [CalculatedFieldType.GEOFENCING, { - name: 'calculated-fields.type.geofencing' + name: 'calculated-fields.type.geofencing', + hint: 'calculated-fields.type.geofencing-hint', }], [CalculatedFieldType.PROPAGATION, { - name: 'calculated-fields.type.propagation' + name: 'calculated-fields.type.propagation', + hint: 'calculated-fields.type.propagation-hint', }], [CalculatedFieldType.RELATED_ENTITIES_AGGREGATION, { name: 'calculated-fields.type.related-entities-aggregation', diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 259008791e..e6ce6ccdc0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1104,11 +1104,15 @@ "selected-fields": "{ count, plural, =1 {1 calculated field} other {# calculated fields} } selected", "type": { "simple": "Simple", + "simple-hint": "Simple arithmetic calculation based on input arguments.", "script": "Script", + "script-hint": "Calculation over defined arguments using a TBEL script.", "geofencing" : "Geofencing", + "geofencing-hint" : "Evaluation of entity GPS position and transitions against configured geofencing zone groups.", "propagation": "Propagation", + "propagation-hint": "Propagation of data to parent or child entities based on relation direction and type.", "related-entities-aggregation": "Related entities aggregation", - "related-entities-aggregation-hint": "Aggregation of data from related entities.", + "related-entities-aggregation-hint": "Aggregation of latest data from related entities.", "time-series-data-aggregation": "Time series data aggregation", "time-series-data-aggregation-hint": "Aggregation of historical data from a current entity." }, @@ -1151,6 +1155,7 @@ "asset-name": "Asset name", "timeseries": "Time series", "output": "Output", + "output-hint": "Defines how output is processed.", "create": "Create new calculated field", "file": "Calculated field file", "invalid-file-error": "Invalid file format. Please make sure the file is a valid JSON file.", @@ -1217,6 +1222,7 @@ "aggregation-path-related-entities": "Aggregation path to related entities", "deduplication-interval": "Deduplication interval", "deduplication-interval-min": "Deduplication interval should be at least {{ sec }} seconds.", + "deduplication-interval-hint": "Minimum time between telemetry aggregations.", "deduplication-interval-required": "Deduplication interval is required.", "calculated-field-filter-title": "Calculated field filter", "filter-title": "Filter", @@ -1245,6 +1251,7 @@ }, "filtered": "Filtered", "value-source": "Value source", + "value-source-hint": "Defines how the value for aggregation is obtained.", "value-source-type": { "key": "Key", "function": "Function" @@ -1306,9 +1313,10 @@ "aggregate-period-hint-offset-and-so-on": "Your aggregation interval will be: {{ interval }} and so on.", "entity-aggregation": { "argument-hint": "Data will be fetched from current entity.", + "argument-title-hint": "Defines the input arguments used for aggregation.", "argument-setting-hint": "Latest telemetry is the only available argument type for this calculated field.", "aggregation-interval": "Aggregation interval", - "aggregation-interval-hint": "Defines how often to perform aggregation. Example: every 1 hour aggregates data at 00:00, 01:00, 02:00, etc.", + "aggregation-interval-hint": "Defines how often aggregation is performed. Example: every 1 hour aggregates data at 00:00, 01:00, 02:00, etc. Aggregation results are stored with the timestamp corresponding to the start of the aggregation interval.", "apply-offset": "Apply offset to aggregation interval", "apply-offset-hint": "Defines how much to shift the start of each aggregation period (e.g., +10 minutes - 00:10, 01:10).", "offset-value": "Offset value", @@ -1372,11 +1380,16 @@ "zone-group-refresh-interval-min": "Zone group refresh interval should be at least {{ min }} seconds.", "propagation-path-related-entities": "Defines a direct, single-level path to a related entity based on the selected direction and relation type.", "data-propagate": "Defines the data to be propagated from the arguments configured below. 'Arguments only' uses the retrieved data directly, while 'Expression result' calculates a new value from that data.", - "aggregation-path-related-entities": "Defines a single-level aggregation path via direct relations with parent or child entities based on direction and relation type. Only relations between device, asset, customer, and tenant entities are supported.", - "arguments-aggregation": "Defines input parameters used for filtering and aggregation.", + "aggregation-path-related-entities": "Defines a single-level aggregation path via direct relations with parent or child entities, based on direction and relation type. Only relations between device, asset, customer, and tenant entities are supported.", + "arguments-aggregation": "Defines the input arguments used for filtering and aggregation.", "setting-arguments-aggregation": "Data will be fetched from related entities configured in aggregation path.", - "metrics": "Defines metrics aggregated based on the configured arguments.", - "import-invalid-calculated-field-type": "Unable to import calculated field: Invalid calculated field structure." + "metrics": "Defines metrics aggregated based on configured arguments.", + "entity-aggregation-metrics": "Defines metrics aggregated based on configured arguments over the specified time intervals.", + "import-invalid-calculated-field-type": "Unable to import calculated field: Invalid calculated field structure.", + "simple-expression-title": "Arithmetic expression that defines how the calculated value is computed.", + "script-title": "TBEL script that defines the calculation logic and output values.", + "simple-arguments": "Arithmetic expression that defines how the calculated value is computed.", + "script-arguments": "Defines the input arguments available to the script." } }, "alarm-rule": { From 5bf087eec4123625d34861e0af7ea114ac851797 Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Wed, 31 Dec 2025 13:48:59 +0200 Subject: [PATCH 52/62] UI: Fixed for alarm rule and calculated fields page --- .../alarm-rule-dialog.component.html | 4 +- .../alarm-rule-dialog.component.ts | 17 ++++++- .../alarm-rules/alarm-rules.component.html | 3 +- .../alarm-rules/alarm-rules.component.ts | 40 ++++++++++++---- ...f-alarm-rule-condition-dialog.component.ts | 12 +++-- .../cf-alarm-rule-condition.component.html | 12 ++--- .../cf-alarm-rule-condition.component.ts | 47 +++++++++---------- .../calculated-field.component.ts | 26 +++++++++- .../calculated-fields-table-config.ts | 2 +- .../calculated-field-dialog.component.html | 6 +-- .../calculated-field-dialog.component.ts | 12 +++++ ...-geofencing-zone-groups-panel.component.ts | 1 + .../geofencing-configuration.component.ts | 14 ++++-- .../entity/entity-select.component.html | 1 + .../entity/entity-select.component.ts | 9 +++- .../string-items-list.component.html | 4 +- .../components/string-items-list.component.ts | 4 +- .../shared/models/calculated-field.models.ts | 2 +- .../assets/locale/locale.constant-en_US.json | 2 +- 19 files changed, 153 insertions(+), 65 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html index ed936cd60e..2c3767c4c8 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html @@ -59,6 +59,7 @@
@if (!data.entityId) {
@@ -134,6 +135,7 @@ placeholder="{{ 'alarm-rule.alarm-rule-relation-types-list' | translate }}" hint="{{ 'alarm-rule.alarm-rule-relation-types-list-hint' | translate }}" [predefinedValues]="predefinedTypeValues" + allowUserValue formControlName="propagateRelationTypes"> } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts index 854f7fb1a0..f0d3140ed0 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, DestroyRef, Inject, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, Inject, ViewChild, ViewEncapsulation } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -43,6 +43,10 @@ import { RelationTypes } from "@shared/models/relation.models"; import { StringItemsOption } from "@shared/components/string-items-list.component"; import { BaseData } from "@shared/models/base-data"; import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; +import { EntitySelectComponent } from '@shared/components/entity/entity-select.component'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { AssetInfo } from '@shared/models/asset.models'; +import { DeviceInfo } from '@shared/models/device.models'; export interface AlarmRuleDialogData { value?: CalculatedField; @@ -64,6 +68,8 @@ export interface AlarmRuleDialogData { }) export class AlarmRuleDialogComponent extends DialogComponent { + @ViewChild('entitySelect') entitySelect!: EntitySelectComponent; + fieldFormGroup: FormGroup ; additionalDebugActionConfig = this.data.value?.id ? { @@ -80,6 +86,7 @@ export class AlarmRuleDialogComponent extends DialogComponent): void { this.entityName = entity.name; + if (this.isAssignedToCustomer(entity as AssetInfo | DeviceInfo)) { + this.ownerId = (entity as AssetInfo | DeviceInfo).customerId; + } + } + + private isAssignedToCustomer(entity: AssetInfo | DeviceInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; } } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html index 64f4ab652f..32107f6eda 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html @@ -68,7 +68,7 @@ required entityTypeLabel="{{ 'entity.entity-type' | translate }}" [filterAllowedEntityTypes]="false" - [allowedEntityTypes]="calculatedFieldsEntityTypeList" + [allowedEntityTypes]="alarmRuleEntityTypeList" (entityChanged)="changeEntity($event)">
@@ -137,6 +137,7 @@ placeholder="{{ 'alarm-rule.alarm-rule-relation-types-list' | translate }}" hint="{{ 'alarm-rule.alarm-rule-relation-types-list-hint' | translate }}" [predefinedValues]="predefinedTypeValues" + allowUserValue formControlName="propagateRelationTypes"> } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts index fb92f6c4fd..0092bf2132 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts @@ -25,7 +25,6 @@ import { CalculatedFieldArgument, CalculatedFieldConfiguration, CalculatedFieldInfo, - calculatedFieldsEntityTypeList, CalculatedFieldType } from '@shared/models/calculated-field.models'; import { EntityId } from '@shared/models/id/entity-id'; @@ -39,8 +38,17 @@ import { import { TenantId } from '@shared/models/id/tenant-id'; import { StringItemsOption } from '@shared/components/string-items-list.component'; import { RelationTypes } from '@shared/models/relation.models'; -import { AlarmRule, AlarmRuleConditionType, AlarmRuleExpressionType } from '@shared/models/alarm-rule.models'; +import { + AlarmRule, + AlarmRuleConditionType, + alarmRuleEntityTypeList, + AlarmRuleExpressionType +} from '@shared/models/alarm-rule.models'; import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; +import { AssetInfo } from '@shared/models/asset.models'; +import { DeviceInfo } from '@shared/models/device.models'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { EntityService } from '@core/http/entity.service'; @Component({ selector: 'tb-alarm-rules', @@ -55,10 +63,10 @@ export class AlarmRulesComponent extends EntityComponent { - this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false}); - }); + this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false}); if (!entityId) { this.entityForm.get('configuration').disable({emitEvent: false}); } @@ -134,6 +141,7 @@ export class AlarmRulesComponent extends EntityComponent { + if (this.isAssignedToCustomer(entity)) { + this.ownerId = entity.customerId; + } + } + ); + } + } + + private isAssignedToCustomer(entity: AssetInfo | DeviceInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; + } + } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts index 20ad4a4bfb..6f691ecd21 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts @@ -184,11 +184,13 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent { this.updateStaticValueValidator(AlarmRuleConditionType.DURATION, mode); + this.updateSpecText(this.conditionFormGroup.get('type').value); }); this.repeatingDynamicModeControl.valueChanges.pipe( takeUntilDestroyed() ).subscribe((mode) => { this.updateStaticValueValidator(AlarmRuleConditionType.REPEATING, mode); + this.updateSpecText(this.conditionFormGroup.get('type').value); }); this.updateValidators(this.conditionFormGroup.get('type').value ?? AlarmRuleConditionType.SIMPLE); @@ -289,20 +291,20 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent
{{ 'alarm-rule.condition' | translate }}
-
{{ 'alarm-rule.schedule-title' | translate }}
-
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts index 7a2ad22fdd..0fcb6d0135 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts @@ -137,23 +137,27 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali writeValue(value: AlarmRuleCondition): void { this.modelValue = value; this.updateConditionInfo(); - if (value && !this.disabled) { - if (this.validate(this.alarmRuleConditionFormGroup)) { - this.onValidatorChange(); - } - } + this.recalculateArgumentValidity(); } ngOnChanges(changes: SimpleChanges) { if (changes.arguments) { - if (changes.arguments && !changes.arguments.firstChange && this.modelValue && !this.disabled) { - if (this.validate(this.alarmRuleConditionFormGroup)) { - this.onValidatorChange(); - } + if (changes.arguments && !changes.arguments.firstChange) { + this.recalculateArgumentValidity(); } } } + private recalculateArgumentValidity(): void { + if (!this.modelValue || !this.arguments) { + this.filtersArgumentsValid = true; + this.schedulerArgumentsValid = true; + return; + } + this.filtersArgumentsValid = this.areFilterAndPredicateArgumentsValid(this.modelValue, this.arguments); + this.schedulerArgumentsValid = this.isScheduleArgumentValid(this.modelValue, Object.keys(this.arguments)); + } + private isScheduleArgumentValid(obj: any, validArguments: string[]): boolean { const arg = obj?.schedule?.dynamicValueArgument; return !arg || validArguments.includes(arg); @@ -177,21 +181,14 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali return true; } - public conditionSet() { - return this.modelValue && (this.modelValue.expression?.expression || this.modelValue.expression?.filters); + public conditionSet(): boolean { + return !!this.modelValue && !!(this.modelValue.expression?.expression || this.modelValue.expression?.filters); } public validate(control: AbstractControl): ValidationErrors | null { - this.filtersArgumentsValid = this.areFilterAndPredicateArgumentsValid(this.modelValue, this.arguments); - this.schedulerArgumentsValid = this.isScheduleArgumentValid(this.modelValue, Object.keys(this.arguments)); - this.onValidatorChange = () => { - control.updateValueAndValidity({ emitEvent: !this.disabled }); - }; - return this.conditionSet() && this.filtersArgumentsValid && this.schedulerArgumentsValid ? null : { - alarmRuleCondition: { - valid: false, - } - }; + const hasCondition = this.conditionSet(); + const argsValid = this.filtersArgumentsValid && this.schedulerArgumentsValid; + return (hasCondition && argsValid) ? null : { alarmRuleCondition: { valid: false } }; } public openFilterDialog($event: Event) { @@ -212,7 +209,6 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali if (result) { this.modelValue = {...this.modelValue, ...result}; this.updateModel(); - this.cd.detectChanges(); } }); } @@ -278,10 +274,10 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali private updateModel() { this.updateConditionInfo(); + this.recalculateArgumentValidity(); this.propagateChange(this.modelValue); - if (this.modelValue) { - this.onValidatorChange(); - } + this.onValidatorChange(); + this.cd.detectChanges(); } public openScheduleDialog($event: Event) { @@ -301,7 +297,6 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali if (result) { this.modelValue.schedule = result; this.updateModel(); - this.cd.detectChanges(); } }); } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts index 65595fe9b8..fcf20b5421 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts @@ -39,6 +39,10 @@ import { } from '@home/components/calculated-fields/calculated-fields-table-config'; import { TenantId } from '@shared/models/id/tenant-id'; import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; +import { AssetInfo } from '@shared/models/asset.models'; +import { DeviceInfo } from '@shared/models/device.models'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { EntityService } from '@core/http/entity.service'; @Component({ selector: 'tb-calculated-field', @@ -55,7 +59,7 @@ export class CalculatedFieldComponent extends EntityComponent { + if (this.isAssignedToCustomer(entity)) { + this.ownerId = entity.customerId; + } + } + ); + } + } + + private isAssignedToCustomer(entity: AssetInfo | DeviceInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; + } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts index e09949d2c6..49cc6b75ca 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts @@ -333,7 +333,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { - if (calculatedField.type === CalculatedFieldType.ALARM) { + if (calculatedField.type === CalculatedFieldType.ALARM || !CalculatedFieldType[calculatedField.type]) { this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('calculated-fields.hint.import-invalid-calculated-field-type'), type: 'error', diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html index 1c4a2995b5..4aba05c99e 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html @@ -94,7 +94,7 @@ [class.tb-form-panel-disabled]="disabledConfiguration" [entityId]="entityId" [entityName]="entityName" - [ownerId]="data.ownerId" + [ownerId]="ownerId" [tenantId]="data.tenantId" > } @@ -104,7 +104,7 @@ [entityId]="entityId" [entityName]="entityName" [tenantId]="data.tenantId" - [ownerId]="data.ownerId" + [ownerId]="ownerId" [testScript]="onTestScript.bind(this)" > } @@ -130,7 +130,7 @@ [class.tb-form-panel-disabled]="disabledConfiguration" [entityId]="entityId" [entityName]="entityName" - [ownerId]="data.ownerId" + [ownerId]="ownerId" [tenantId]="data.tenantId" [isScript]="fieldFormGroup.get('type').value === CalculatedFieldType.SCRIPT" [testScript]="onTestScript.bind(this)" diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts index ee38fb2a2d..7f1c9e376a 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts @@ -39,6 +39,9 @@ import { deepTrim } from '@core/utils'; import { BaseData } from '@shared/models/base-data'; import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; import { FormGroup } from '@angular/forms'; +import { AssetInfo } from '@shared/models/asset.models'; +import { DeviceInfo } from '@shared/models/device.models'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; export interface CalculatedFieldDialogData { value?: CalculatedField; @@ -69,6 +72,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent): void { this.entityName = entity.name; + if (this.isAssignedToCustomer(entity as AssetInfo | DeviceInfo)) { + this.ownerId = (entity as AssetInfo | DeviceInfo).customerId; + } } get entityId(): EntityId { @@ -163,4 +171,8 @@ export class CalculatedFieldDialogComponent extends DialogComponent ): void { this.entityChanged.emit(entity); } + + entityAutocompleteMarkAsTouched() { + this.entityAutocompleteComponent.markAsTouched(); + } } diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.html b/ui-ngx/src/app/shared/components/string-items-list.component.html index 0469be2589..bcd0076269 100644 --- a/ui-ngx/src/app/shared/components/string-items-list.component.html +++ b/ui-ngx/src/app/shared/components/string-items-list.component.html @@ -52,7 +52,9 @@ @for (value of filteredValues | async; track value.value) { } @empty { - {{ 'common.not-found' | translate }} + @if (!allowUserValue) { + {{ 'common.not-found' | translate }} + } } diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.ts b/ui-ngx/src/app/shared/components/string-items-list.component.ts index 1c210fdf1f..ab9f1f5831 100644 --- a/ui-ngx/src/app/shared/components/string-items-list.component.ts +++ b/ui-ngx/src/app/shared/components/string-items-list.component.ts @@ -210,8 +210,8 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { addOnBlur(event: FocusEvent) { const target: HTMLElement = event.relatedTarget as HTMLElement; - if (target && target.tagName !== 'MAT-OPTION') { - this.addItem(this.stringItemInput.nativeElement.value ?? '') + if ((target && target.tagName !== 'MAT-OPTION') || this.allowUserValue) { + this.addItem(this.stringItemInput.nativeElement.value ?? ''); } this.onTouched(); } diff --git a/ui-ngx/src/app/shared/models/calculated-field.models.ts b/ui-ngx/src/app/shared/models/calculated-field.models.ts index b0c2c1a899..db3da4235e 100644 --- a/ui-ngx/src/app/shared/models/calculated-field.models.ts +++ b/ui-ngx/src/app/shared/models/calculated-field.models.ts @@ -525,7 +525,7 @@ export const ArgumentEntityTypeParamsMap =new Map { - switch (entityId.entityType) { + switch (entityId?.entityType) { case EntityType.ASSET_PROFILE: return { assetTypes: [entityName], diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index e6ce6ccdc0..eaad687821 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1434,7 +1434,7 @@ "condition-during": "During {{during}}", "condition-during-dynamic": "During \"{{ attribute }}\"", "condition-repeat-times": "Repeats { count, plural, =1 {1 time} other {# times} }", - "condition-repeat-times-dynamic": "Repeats \"{{ attribute }}\"", + "condition-repeat-times-dynamic": "Repeats \"{{ attribute }}\" times", "filter-preview": "Filter preview", "condition-settings": "Condition settings", "static": "Static", From 276c3064b029c3103268192b1d32cc743321e277 Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Fri, 2 Jan 2026 16:38:27 +0200 Subject: [PATCH 53/62] UI: Preview for argument table --- ...ulated-field-argument-panel.component.html | 16 ++++--- ...lculated-field-argument-panel.component.ts | 6 +++ ...lated-field-arguments-table.component.html | 43 ++++++++++++------- ...culated-field-arguments-table.component.ts | 3 +- ...eofencing-zone-groups-panel.component.html | 16 ++++--- ...-geofencing-zone-groups-panel.component.ts | 5 +++ ...eofencing-zone-groups-table.component.html | 39 +++++++++++------ ...-geofencing-zone-groups-table.component.ts | 3 +- ...culated-field-metrics-panel.component.html | 20 +++++---- ...alculated-field-metrics-panel.component.ts | 7 ++- ...culated-field-metrics-table.component.html | 29 +++++++++---- ...alculated-field-metrics-table.component.ts | 5 ++- .../assets/locale/locale.constant-en_US.json | 1 + 13 files changed, 126 insertions(+), 67 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.html index 30533dc714..484f1f9238 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.html @@ -188,13 +188,15 @@ (click)="cancel()"> {{ 'action.cancel' | translate }} - + @if (!readonly) { + + }
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.ts index 51d14c9478..d6271409f8 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-argument-panel.component.ts @@ -88,6 +88,7 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI maxlength: 'calculated-fields.hint.argument-name-max-length', forbidden: 'calculated-fields.hint.argument-name-forbidden' }; + @Input() readonly = false; @ViewChild('entityAutocomplete') entityAutocomplete: EntityAutocompleteComponent; @@ -176,6 +177,11 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI this.argumentTypes = Object.values(ArgumentType) .filter(type => type !== ArgumentType.Rolling || this.isScript); + + if (this.readonly) { + this.argumentType.disable({emitEvent: false}); + this.argumentFormGroup.disable({emitEvent: false}); + } } ngAfterViewInit(): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html index 99b4225db2..c61ce4c467 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html @@ -93,22 +93,33 @@
- + @if (disable) { + + } @else { + + } - + @if (!readonly) { + + }
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts index 3484e717cb..9216eaa12c 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts @@ -71,6 +71,7 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit @Input() entityName: string; @Input() ownerId: EntityId; @Input() usedNames: string[]; + @Input() readonly = false; @ViewChild('entityAutocomplete') entityAutocomplete: EntityAutocompleteComponent; @@ -160,6 +161,10 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit this.currentEntityFilter = getCalculatedFieldCurrentEntityFilter(this.entityName, this.entityId); this.updateEntityFilter(this.zone.refEntityId?.entityType); + + if (this.readonly) { + this.geofencingFormGroup.disable({emitEvent: false}); + } } fetchOptions(searchText: string): Observable> { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html index fa0ba32f22..d661f5021a 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html @@ -96,20 +96,31 @@
- + @if (disable) { + + } @else { + + } @@ -119,7 +119,7 @@
@@ -213,12 +213,14 @@ (click)="cancel()"> {{ 'action.cancel' | translate }} - + @if (!readonly) { + + }
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts index 811f7b87fa..49dca514d2 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts @@ -42,7 +42,7 @@ interface CalculatedFieldAggMetricValuePanel extends CalculatedFieldAggMetricVal @Component({ selector: 'tb-calculated-field-metrics-panel', templateUrl: './calculated-field-metrics-panel.component.html', - styleUrl: '../common/calculated-field-panel.scss', + styleUrls: ['../common/calculated-field-panel.scss', '../../calculated-field.component.scss'], }) export class CalculatedFieldMetricsPanelComponent implements OnInit { @@ -54,6 +54,7 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit { @Input() editorCompleter: TbEditorCompleter; @Input() highlightRules: AceHighlightRules; @Input({required: true}) testScript: (expression?: string) => Observable; + @Input() readonly = false; metricDataApplied = output(); filterExpanded = false; @@ -107,6 +108,10 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit { if (this.simpleMode) { this.AggFunctions = this.AggFunctions.filter(aggFunc => aggFunc !== AggFunction.COUNT_UNIQUE); } + + if (this.readonly) { + this.metricForm.disable({emitEvent: false}); + } } saveMetric(): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.html index 4693d3afb4..3b50b9e152 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.html @@ -93,15 +93,26 @@
- + @if (disable) { + + } @else { + + }
} @else { - } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts index 94b9e19056..1ec89e411a 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts @@ -326,6 +326,6 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit } get dragEnabled(): boolean { - return this.levelsFormArray().controls.length > 1; + return this.levelsFormArray().controls.length > 1 && !this.readonly; } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.html index 8d6fc37073..1eeb4d4d5d 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.html @@ -182,7 +182,7 @@ matTooltip="{{ 'calculated-fields.test-script-function' | translate }}" matTooltipPosition="above" class="tb-mat-32" - [disabled]="!arguments.length" + [disabled]="!arguments.length || readonly" (click)="onTestScript('input.function')"> bug_report @@ -191,7 +191,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts index 49dca514d2..e8c8c4984a 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-panel.component.ts @@ -24,6 +24,8 @@ import { AggInputType, AggInputTypeTranslations, CalculatedFieldAggMetricValue, + calculatedFieldMetricFilterDefaultScript, + calculatedFieldMetricMapDefaultScript, FORBIDDEN_NAMES, forbiddenNamesValidator, uniqueNameValidator @@ -64,11 +66,11 @@ export class CalculatedFieldMetricsPanelComponent implements OnInit { name: ['', [Validators.required, forbiddenNamesValidator(FORBIDDEN_NAMES), Validators.pattern(charsWithNumRegex), Validators.maxLength(255)]], function: [AggFunction.AVG], allowFilter: [false], - filter: ['', Validators.required], + filter: [calculatedFieldMetricFilterDefaultScript, Validators.required], input: this.fb.group({ type: [AggInputType.key], key: ['', Validators.required], - function: ['', Validators.required], + function: [calculatedFieldMetricMapDefaultScript, Validators.required], }), defaultValue: [null] }); diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html index c1d55dd03a..b0005cad7a 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html @@ -30,7 +30,7 @@ @if (outputForm.get('type').value === OutputType.Attribute - && (entityId.entityType === EntityType.DEVICE || entityId.entityType === EntityType.DEVICE_PROFILE)) { + && (entityId?.entityType === EntityType.DEVICE || entityId?.entityType === EntityType.DEVICE_PROFILE)) { {{ 'calculated-fields.attribute-scope' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html index 32da6c4e35..828b5eac1e 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html @@ -72,7 +72,9 @@ {{ 'version-control.load-relations' | translate }} - + {{ 'version-control.load-calculated-fields' | translate }}
diff --git a/ui-ngx/src/app/shared/models/calculated-field.models.ts b/ui-ngx/src/app/shared/models/calculated-field.models.ts index db3da4235e..9370e59835 100644 --- a/ui-ngx/src/app/shared/models/calculated-field.models.ts +++ b/ui-ngx/src/app/shared/models/calculated-field.models.ts @@ -1083,3 +1083,14 @@ export interface CalculatedFieldsQuery { entities?: Array; name?: Array; } + +export const calculatedFieldMetricFilterDefaultScript = + '// Sample filter script to include only active and unoccupied parking spaces\n' + + '// Goal: Count only parking spaces that are active and currently free\n\n' + + 'return active == true && occupied == false;'; + +export const calculatedFieldMetricMapDefaultScript = + '// Sample map script to convert temperature from Fahrenheit to Celsius\n' + + '// Goal: Apply conversion per entity before aggregation (e.g., for average temperature)\n\n' + + 'var temperatureC = (temperature - 32) / 1.8;\n' + + 'return toFixed(temperatureC, 2);'; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 26a2c938de..5e083ec165 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1552,7 +1552,7 @@ "mode": "Mode", "type": "Type", "value-required": "Value is required.", - "min-value": "Value must be 0 or greater.", + "min-value": "Value must be 1 or greater.", "argument-in-use": "Argument is used as general argument.", "import-invalid-alarm-rule-type": "Unable to import alarm rule: Invalid alarm rule structure.", "no-filter-preview": "No filter specified", @@ -7257,7 +7257,7 @@ "load-relations": "Load relations", "load-attributes": "Load attributes", "load-credentials": "Load credentials", - "load-calculated-fields": "Load calculated fields", + "load-calculated-fields": "Load calculated fields and alarm rules", "compare-with-current": "Compare with current", "diff-entity-with-version": "Diff with entity version '{{versionName}}'", "previous-difference": "Previous Difference", From 4afae09a09fd83729723a1ac31171f93b3ee6ec2 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 6 Jan 2026 13:05:50 +0200 Subject: [PATCH 61/62] UI: Fixed background flashes when opening the date-time selector --- .../@mat-datetimepicker+core+14.0.0.patch | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/ui-ngx/patches/@mat-datetimepicker+core+14.0.0.patch b/ui-ngx/patches/@mat-datetimepicker+core+14.0.0.patch index 293ac10a21..ac4f7434e9 100644 --- a/ui-ngx/patches/@mat-datetimepicker+core+14.0.0.patch +++ b/ui-ngx/patches/@mat-datetimepicker+core+14.0.0.patch @@ -15,8 +15,39 @@ index 7ecfae7..08363d3 100644 date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), value); } this._timeChanged = true; +diff --git a/node_modules/@mat-datetimepicker/core/esm2022/datetimepicker/datetimepicker.mjs b/node_modules/@mat-datetimepicker/core/esm2022/datetimepicker/datetimepicker.mjs +index 5b82377..30e35ce 100644 +--- a/node_modules/@mat-datetimepicker/core/esm2022/datetimepicker/datetimepicker.mjs ++++ b/node_modules/@mat-datetimepicker/core/esm2022/datetimepicker/datetimepicker.mjs +@@ -48,7 +48,7 @@ export class MatDatetimepickerContentComponent { + } + } + /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: MatDatetimepickerContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } +- /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.4", type: MatDatetimepickerContentComponent, selector: "mat-datetimepicker-content", host: { listeners: { "keydown": "_handleKeydown($event)" }, properties: { "class.mat-datetimepicker-content-touch": "datetimepicker?.touchUi" }, classAttribute: "mat-datetimepicker-content" }, viewQueries: [{ propertyName: "_calendar", first: true, predicate: MatDatetimepickerCalendarComponent, descendants: true, static: true }], ngImport: i0, template: "\n\n", styles: [".mat-datetimepicker-content{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;display:block;background-color:#fff;border-radius:2px;overflow:hidden}.mat-datetimepicker-calendar{width:296px;height:405px}.mat-datetimepicker-calendar[mode=landscape]{width:446px;height:328px}@media (min-width: 480px){.mat-datetimepicker-calendar[mode=auto]{width:446px;height:328px}}.mat-datetimepicker-content-touch{box-shadow:0 0 #0003,0 0 #00000024,0 0 #0000001f;display:block;box-shadow:0 11px 15px -7px #0003,0 24px 38px 3px #00000024,0 9px 46px 8px #0000001f}.cdk-overlay-container,.cdk-global-overlay-wrapper{pointer-events:none;top:0;left:0;height:100%;width:100%}.cdk-overlay-container{position:fixed;z-index:1000}.cdk-global-overlay-wrapper{display:flex;position:absolute;z-index:1000}.cdk-overlay-pane{position:absolute;pointer-events:auto;box-sizing:border-box;z-index:1000}.cdk-overlay-backdrop{position:absolute;inset:0;z-index:1000;pointer-events:auto;transition:opacity .4s cubic-bezier(.25,.8,.25,1);opacity:0}.cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:.48}.cdk-overlay-dark-backdrop{background:#0009}.mat-datetimepicker-dialog .mat-dialog-container{padding:0}\n"], dependencies: [{ kind: "directive", type: i1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: i2.MatDatetimepickerCalendarComponent, selector: "mat-datetimepicker-calendar", inputs: ["multiYearSelector", "startView", "twelvehour", "timeInterval", "dateFilter", "ariaLabel", "ariaNextMonthLabel", "ariaPrevMonthLabel", "ariaNextYearLabel", "ariaPrevYearLabel", "ariaNextMultiYearLabel", "ariaPrevMultiYearLabel", "preventSameDateTimeSelection", "type", "startAt", "selected", "minDate", "maxDate"], outputs: ["_userSelection", "selectedChange", "viewChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } ++ /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.4", type: MatDatetimepickerContentComponent, selector: "mat-datetimepicker-content", host: { listeners: { "keydown": "_handleKeydown($event)" }, properties: { "class.mat-datetimepicker-content-touch": "datetimepicker?.touchUi" }, classAttribute: "mat-datetimepicker-content" }, viewQueries: [{ propertyName: "_calendar", first: true, predicate: MatDatetimepickerCalendarComponent, descendants: true, static: true }], ngImport: i0, template: "\n\n", styles: [".mat-datetimepicker-content{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;display:block;background-color:#fff;border-radius:2px;overflow:hidden}.mat-datetimepicker-calendar{width:296px;height:405px}.mat-datetimepicker-calendar[mode=landscape]{width:446px;height:328px}@media (min-width: 480px){.mat-datetimepicker-calendar[mode=auto]{width:446px;height:328px}}.mat-datetimepicker-content-touch{box-shadow:0 0 #0003,0 0 #00000024,0 0 #0000001f;display:block;box-shadow:0 11px 15px -7px #0003,0 24px 38px 3px #00000024,0 9px 46px 8px #0000001f}.mat-datetimepicker-dialog .mat-dialog-container{padding:0}\n"], dependencies: [{ kind: "directive", type: i1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: i2.MatDatetimepickerCalendarComponent, selector: "mat-datetimepicker-calendar", inputs: ["multiYearSelector", "startView", "twelvehour", "timeInterval", "dateFilter", "ariaLabel", "ariaNextMonthLabel", "ariaPrevMonthLabel", "ariaNextYearLabel", "ariaPrevYearLabel", "ariaNextMultiYearLabel", "ariaPrevMultiYearLabel", "preventSameDateTimeSelection", "type", "startAt", "selected", "minDate", "maxDate"], outputs: ["_userSelection", "selectedChange", "viewChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } + } + i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: MatDatetimepickerContentComponent, decorators: [{ + type: Component, +@@ -56,7 +56,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImpor + class: 'mat-datetimepicker-content', + '[class.mat-datetimepicker-content-touch]': 'datetimepicker?.touchUi', + '(keydown)': '_handleKeydown($event)', +- }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "\n\n", styles: [".mat-datetimepicker-content{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;display:block;background-color:#fff;border-radius:2px;overflow:hidden}.mat-datetimepicker-calendar{width:296px;height:405px}.mat-datetimepicker-calendar[mode=landscape]{width:446px;height:328px}@media (min-width: 480px){.mat-datetimepicker-calendar[mode=auto]{width:446px;height:328px}}.mat-datetimepicker-content-touch{box-shadow:0 0 #0003,0 0 #00000024,0 0 #0000001f;display:block;box-shadow:0 11px 15px -7px #0003,0 24px 38px 3px #00000024,0 9px 46px 8px #0000001f}.cdk-overlay-container,.cdk-global-overlay-wrapper{pointer-events:none;top:0;left:0;height:100%;width:100%}.cdk-overlay-container{position:fixed;z-index:1000}.cdk-global-overlay-wrapper{display:flex;position:absolute;z-index:1000}.cdk-overlay-pane{position:absolute;pointer-events:auto;box-sizing:border-box;z-index:1000}.cdk-overlay-backdrop{position:absolute;inset:0;z-index:1000;pointer-events:auto;transition:opacity .4s cubic-bezier(.25,.8,.25,1);opacity:0}.cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:.48}.cdk-overlay-dark-backdrop{background:#0009}.mat-datetimepicker-dialog .mat-dialog-container{padding:0}\n"] }] ++ }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "\n\n", styles: [".mat-datetimepicker-content{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;display:block;background-color:#fff;border-radius:2px;overflow:hidden}.mat-datetimepicker-calendar{width:296px;height:405px}.mat-datetimepicker-calendar[mode=landscape]{width:446px;height:328px}@media (min-width: 480px){.mat-datetimepicker-calendar[mode=auto]{width:446px;height:328px}}.mat-datetimepicker-content-touch{box-shadow:0 0 #0003,0 0 #00000024,0 0 #0000001f;display:block;box-shadow:0 11px 15px -7px #0003,0 24px 38px 3px #00000024,0 9px 46px 8px #0000001f}.mat-datetimepicker-dialog .mat-dialog-container{padding:0}\n"] }] + }], propDecorators: { _calendar: [{ + type: ViewChild, + args: [MatDatetimepickerCalendarComponent, { static: true }] +@@ -301,7 +301,7 @@ export class MatDatetimepickerComponent { + const overlayConfig = new OverlayConfig({ + positionStrategy: this._createPopupPositionStrategy(), + hasBackdrop: true, +- backdropClass: 'mat-overlay-transparent-backdrop', ++ backdropClass: 'cdk-overlay-transparent-backdrop', + direction: this._dir ? this._dir.value : 'ltr', + scrollStrategy: this._scrollStrategy(), + panelClass: 'mat-datetimepicker-popup', diff --git a/node_modules/@mat-datetimepicker/core/fesm2022/mat-datetimepicker-core.mjs b/node_modules/@mat-datetimepicker/core/fesm2022/mat-datetimepicker-core.mjs -index 00f4a52..df688e3 100644 +index 00f4a52..99c0ac1 100644 --- a/node_modules/@mat-datetimepicker/core/fesm2022/mat-datetimepicker-core.mjs +++ b/node_modules/@mat-datetimepicker/core/fesm2022/mat-datetimepicker-core.mjs @@ -946,9 +946,9 @@ class MatDatetimepickerClockComponent { @@ -32,3 +63,30 @@ index 00f4a52..df688e3 100644 date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), value); } this._timeChanged = true; +@@ -1911,7 +1911,7 @@ class MatDatetimepickerContentComponent { + } + } + /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: MatDatetimepickerContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } +- /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.4", type: MatDatetimepickerContentComponent, selector: "mat-datetimepicker-content", host: { listeners: { "keydown": "_handleKeydown($event)" }, properties: { "class.mat-datetimepicker-content-touch": "datetimepicker?.touchUi" }, classAttribute: "mat-datetimepicker-content" }, viewQueries: [{ propertyName: "_calendar", first: true, predicate: MatDatetimepickerCalendarComponent, descendants: true, static: true }], ngImport: i0, template: "\n\n", styles: [".mat-datetimepicker-content{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;display:block;background-color:#fff;border-radius:2px;overflow:hidden}.mat-datetimepicker-calendar{width:296px;height:405px}.mat-datetimepicker-calendar[mode=landscape]{width:446px;height:328px}@media (min-width: 480px){.mat-datetimepicker-calendar[mode=auto]{width:446px;height:328px}}.mat-datetimepicker-content-touch{box-shadow:0 0 #0003,0 0 #00000024,0 0 #0000001f;display:block;box-shadow:0 11px 15px -7px #0003,0 24px 38px 3px #00000024,0 9px 46px 8px #0000001f}.cdk-overlay-container,.cdk-global-overlay-wrapper{pointer-events:none;top:0;left:0;height:100%;width:100%}.cdk-overlay-container{position:fixed;z-index:1000}.cdk-global-overlay-wrapper{display:flex;position:absolute;z-index:1000}.cdk-overlay-pane{position:absolute;pointer-events:auto;box-sizing:border-box;z-index:1000}.cdk-overlay-backdrop{position:absolute;inset:0;z-index:1000;pointer-events:auto;transition:opacity .4s cubic-bezier(.25,.8,.25,1);opacity:0}.cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:.48}.cdk-overlay-dark-backdrop{background:#0009}.mat-datetimepicker-dialog .mat-dialog-container{padding:0}\n"], dependencies: [{ kind: "directive", type: i1$2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: MatDatetimepickerCalendarComponent, selector: "mat-datetimepicker-calendar", inputs: ["multiYearSelector", "startView", "twelvehour", "timeInterval", "dateFilter", "ariaLabel", "ariaNextMonthLabel", "ariaPrevMonthLabel", "ariaNextYearLabel", "ariaPrevYearLabel", "ariaNextMultiYearLabel", "ariaPrevMultiYearLabel", "preventSameDateTimeSelection", "type", "startAt", "selected", "minDate", "maxDate"], outputs: ["_userSelection", "selectedChange", "viewChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } ++ /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.4", type: MatDatetimepickerContentComponent, selector: "mat-datetimepicker-content", host: { listeners: { "keydown": "_handleKeydown($event)" }, properties: { "class.mat-datetimepicker-content-touch": "datetimepicker?.touchUi" }, classAttribute: "mat-datetimepicker-content" }, viewQueries: [{ propertyName: "_calendar", first: true, predicate: MatDatetimepickerCalendarComponent, descendants: true, static: true }], ngImport: i0, template: "\n\n", styles: [".mat-datetimepicker-content{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;display:block;background-color:#fff;border-radius:2px;overflow:hidden}.mat-datetimepicker-calendar{width:296px;height:405px}.mat-datetimepicker-calendar[mode=landscape]{width:446px;height:328px}@media (min-width: 480px){.mat-datetimepicker-calendar[mode=auto]{width:446px;height:328px}}.mat-datetimepicker-content-touch{box-shadow:0 0 #0003,0 0 #00000024,0 0 #0000001f;display:block;box-shadow:0 11px 15px -7px #0003,0 24px 38px 3px #00000024,0 9px 46px 8px #0000001f}.mat-datetimepicker-dialog .mat-dialog-container{padding:0}\n"], dependencies: [{ kind: "directive", type: i1$2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: MatDatetimepickerCalendarComponent, selector: "mat-datetimepicker-calendar", inputs: ["multiYearSelector", "startView", "twelvehour", "timeInterval", "dateFilter", "ariaLabel", "ariaNextMonthLabel", "ariaPrevMonthLabel", "ariaNextYearLabel", "ariaPrevYearLabel", "ariaNextMultiYearLabel", "ariaPrevMultiYearLabel", "preventSameDateTimeSelection", "type", "startAt", "selected", "minDate", "maxDate"], outputs: ["_userSelection", "selectedChange", "viewChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } + } + i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImport: i0, type: MatDatetimepickerContentComponent, decorators: [{ + type: Component, +@@ -1919,7 +1919,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.4", ngImpor + class: 'mat-datetimepicker-content', + '[class.mat-datetimepicker-content-touch]': 'datetimepicker?.touchUi', + '(keydown)': '_handleKeydown($event)', +- }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "\n\n", styles: [".mat-datetimepicker-content{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;display:block;background-color:#fff;border-radius:2px;overflow:hidden}.mat-datetimepicker-calendar{width:296px;height:405px}.mat-datetimepicker-calendar[mode=landscape]{width:446px;height:328px}@media (min-width: 480px){.mat-datetimepicker-calendar[mode=auto]{width:446px;height:328px}}.mat-datetimepicker-content-touch{box-shadow:0 0 #0003,0 0 #00000024,0 0 #0000001f;display:block;box-shadow:0 11px 15px -7px #0003,0 24px 38px 3px #00000024,0 9px 46px 8px #0000001f}.cdk-overlay-container,.cdk-global-overlay-wrapper{pointer-events:none;top:0;left:0;height:100%;width:100%}.cdk-overlay-container{position:fixed;z-index:1000}.cdk-global-overlay-wrapper{display:flex;position:absolute;z-index:1000}.cdk-overlay-pane{position:absolute;pointer-events:auto;box-sizing:border-box;z-index:1000}.cdk-overlay-backdrop{position:absolute;inset:0;z-index:1000;pointer-events:auto;transition:opacity .4s cubic-bezier(.25,.8,.25,1);opacity:0}.cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:.48}.cdk-overlay-dark-backdrop{background:#0009}.mat-datetimepicker-dialog .mat-dialog-container{padding:0}\n"] }] ++ }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "\n\n", styles: [".mat-datetimepicker-content{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;display:block;background-color:#fff;border-radius:2px;overflow:hidden}.mat-datetimepicker-calendar{width:296px;height:405px}.mat-datetimepicker-calendar[mode=landscape]{width:446px;height:328px}@media (min-width: 480px){.mat-datetimepicker-calendar[mode=auto]{width:446px;height:328px}}.mat-datetimepicker-content-touch{box-shadow:0 0 #0003,0 0 #00000024,0 0 #0000001f;display:block;box-shadow:0 11px 15px -7px #0003,0 24px 38px 3px #00000024,0 9px 46px 8px #0000001f}.mat-datetimepicker-dialog .mat-dialog-container{padding:0}\n"] }] + }], propDecorators: { _calendar: [{ + type: ViewChild, + args: [MatDatetimepickerCalendarComponent, { static: true }] +@@ -2164,7 +2164,7 @@ class MatDatetimepickerComponent { + const overlayConfig = new OverlayConfig({ + positionStrategy: this._createPopupPositionStrategy(), + hasBackdrop: true, +- backdropClass: 'mat-overlay-transparent-backdrop', ++ backdropClass: 'cdk-overlay-transparent-backdrop', + direction: this._dir ? this._dir.value : 'ltr', + scrollStrategy: this._scrollStrategy(), + panelClass: 'mat-datetimepicker-popup', From 578c88e59a08363d01dbb30c08e851c606cf4f3c Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 6 Jan 2026 14:12:24 +0200 Subject: [PATCH 62/62] Fixed map action panel hide when open other data layer --- .../components/widget/lib/maps/data-layer/data-layer-utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/data-layer-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/data-layer-utils.ts index efaf3808b2..895b2f9bb5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/data-layer-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/data-layer-utils.ts @@ -44,6 +44,7 @@ export const createTooltip = (map: TbMap, tooltip.close(); } else if (canOpen()) { if ((tooltip as any)._prepareOpen((layer as any)._latlng)) { + map.deselectItem(); tooltip.openOn(map.getMap()); } }