From e3867486b5b7e6548b0751c1ecb97a6702d91984 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Jun 2023 10:18:18 +0200 Subject: [PATCH 01/51] fixed update inactivity timeout attribute --- .../server/service/state/DefaultDeviceStateService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 554359fd6d..57765b613d 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -231,7 +231,6 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService Date: Wed, 28 Jun 2023 22:33:03 +0200 Subject: [PATCH 02/51] added corresponding tests --- .../state/DefaultDeviceStateServiceTest.java | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java index 52f2ec5c9d..5a69702405 100644 --- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java @@ -22,20 +22,33 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.TsValue; +import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.sql.query.EntityQueryRepository; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.service.partition.AbstractPartitionBasedService; +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import static org.hamcrest.CoreMatchers.is; @@ -62,6 +75,8 @@ public class DefaultDeviceStateServiceTest { PartitionService partitionService; @Mock DeviceStateData deviceStateDataMock; + @Mock + EntityQueryRepository entityQueryRepository; DeviceId deviceId = DeviceId.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112"); @@ -69,7 +84,7 @@ public class DefaultDeviceStateServiceTest { @Before public void setUp() { - service = spy(new DefaultDeviceStateService(deviceService, attributesService, tsService, clusterService, partitionService, null, null, null, mock(NotificationRuleProcessor.class))); + service = spy(new DefaultDeviceStateService(deviceService, attributesService, tsService, clusterService, partitionService, entityQueryRepository, null, null, mock(NotificationRuleProcessor.class))); } @Test @@ -125,4 +140,56 @@ public class DefaultDeviceStateServiceTest { Assert.assertEquals(5000L, deviceStateData.getState().getInactivityTimeout()); } + @Test + public void givenUpdateInactivityTimeoutAndThenNoStateChange() throws Exception { + TelemetrySubscriptionService telemetrySubscriptionService = Mockito.mock(TelemetrySubscriptionService.class); + ReflectionTestUtils.setField(service, "tsSubService", telemetrySubscriptionService); + ReflectionTestUtils.setField(service, "defaultStateCheckIntervalInSec", 60); + ReflectionTestUtils.setField(service, "defaultActivityStatsIntervalInSec", 60); + ReflectionTestUtils.setField(service, "defaultInactivityTimeoutMs", 60000); + ReflectionTestUtils.setField(service, "initFetchPackSize", 10); + + Mockito.when(entityQueryRepository.findEntityDataByQueryInternal(Mockito.any())).thenReturn(new PageData<>()); + + service.init(); + var tenantId = new TenantId(UUID.randomUUID()); + var tpi = TopicPartitionInfo.builder().myPartition(true).build(); + Mockito.when(partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId)).thenReturn(tpi); + + var deviceIdInfo = new DeviceIdInfo(tenantId.getId(), null, deviceId.getId()); + + Mockito.when(deviceService.findDeviceIdInfos(Mockito.any())) + .thenReturn(new PageData<>(List.of(deviceIdInfo), 0, 1, false)); + + Method method = AbstractPartitionBasedService.class.getDeclaredMethod("initStateFromDB", Set.class); + method.setAccessible(true); + method.invoke(service, Collections.singleton(tpi)); + + service.onAddedPartitions(Collections.singleton(tpi)); + + DeviceState deviceState = DeviceState.builder().build(); + + DeviceStateData deviceStateData = DeviceStateData.builder() + .tenantId(tenantId) + .deviceId(deviceId) + .state(deviceState) + .metaData(new TbMsgMetaData()) + .build(); + + service.deviceStates.put(deviceId, deviceStateData); + + service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); + + Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(true), Mockito.any()); + + Mockito.reset(telemetrySubscriptionService); + + service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, 1); + + Mockito.verify(telemetrySubscriptionService, Mockito.never()).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(false), Mockito.any()); + service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, 60000); + + Mockito.verify(telemetrySubscriptionService, Mockito.never()).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(true), Mockito.any()); + } + } \ No newline at end of file From c3293f556e77a54afdad1ade4a0b8fc557a29ddd Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 6 Jul 2023 11:40:50 +0200 Subject: [PATCH 03/51] inactivity improvements --- .../state/DefaultDeviceStateService.java | 12 +++++-- .../state/DefaultDeviceStateServiceTest.java | 35 ++++++++++++++++--- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 57765b613d..65bb01533b 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -284,6 +284,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService= deviceState.getLastActivityTime()) { + deviceState.setLastInactivityAlarmTime(0L); + save(deviceId, INACTIVITY_ALARM_TIME, 0L); + } } } } diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java index 5a69702405..631a82f518 100644 --- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java @@ -141,12 +141,12 @@ public class DefaultDeviceStateServiceTest { } @Test - public void givenUpdateInactivityTimeoutAndThenNoStateChange() throws Exception { + public void givenIncreaseInactivityTimeoutAndThenStateIsActive() throws Exception { TelemetrySubscriptionService telemetrySubscriptionService = Mockito.mock(TelemetrySubscriptionService.class); ReflectionTestUtils.setField(service, "tsSubService", telemetrySubscriptionService); ReflectionTestUtils.setField(service, "defaultStateCheckIntervalInSec", 60); ReflectionTestUtils.setField(service, "defaultActivityStatsIntervalInSec", 60); - ReflectionTestUtils.setField(service, "defaultInactivityTimeoutMs", 60000); + ReflectionTestUtils.setField(service, "defaultInactivityTimeoutMs", 1); ReflectionTestUtils.setField(service, "initFetchPackSize", 10); Mockito.when(entityQueryRepository.findEntityDataByQueryInternal(Mockito.any())).thenReturn(new PageData<>()); @@ -178,18 +178,43 @@ public class DefaultDeviceStateServiceTest { service.deviceStates.put(deviceId, deviceStateData); + service.getPartitionedEntities(tpi).add(deviceId); + service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(true), Mockito.any()); + Thread.sleep(1); + + service.checkStates(); + + Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(false), Mockito.any()); + Mockito.reset(telemetrySubscriptionService); - service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, 1); + service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, System.currentTimeMillis() - deviceState.getLastActivityTime() + 1000); - Mockito.verify(telemetrySubscriptionService, Mockito.never()).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(false), Mockito.any()); - service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, 60000); + Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(true), Mockito.any()); + + Thread.sleep(2000); + + service.checkStates(); + + Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(false), Mockito.any()); + + Mockito.reset(telemetrySubscriptionService); + + service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, 2000); Mockito.verify(telemetrySubscriptionService, Mockito.never()).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(true), Mockito.any()); + + service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); + + Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(true), Mockito.any()); + + service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, 1); + + Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(false), Mockito.any()); } } \ No newline at end of file From 4b7cc4571dbe91d27cb0428d09b90603ae70410c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 12 Jul 2023 19:48:25 +0200 Subject: [PATCH 04/51] added deleteLatest api --- .../controller/TelemetryController.java | 74 ++++++++-- .../DefaultTelemetrySubscriptionService.java | 7 + .../server/controller/AbstractWebTest.java | 13 ++ .../controller/TelemetryControllerTest.java | 137 ++++++++++++++++++ .../common/data/kv/BaseDeleteTsKvQuery.java | 10 +- .../common/data/kv/DeleteTsKvQuery.java | 2 + .../dao/timeseries/BaseTimeseriesService.java | 4 +- .../thingsboard/rest/client/RestClient.java | 22 ++- .../api/RuleEngineTelemetryService.java | 2 + 9 files changed, 257 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 05f56e93bd..8d94fb22cf 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -201,7 +201,7 @@ public class TelemetryController extends BaseController { @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope) throws ThingsboardException { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, - (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope)); + (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope)); } @ApiOperation(value = "Get attributes (getAttributes)", @@ -219,9 +219,9 @@ public class TelemetryController extends BaseController { @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { - SecurityUser user = getCurrentUser(); + SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, - (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr)); + (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr)); } @@ -245,7 +245,7 @@ public class TelemetryController extends BaseController { @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, - (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr)); + (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr)); } @ApiOperation(value = "Get time-series keys (getTimeseriesKeys)", @@ -259,7 +259,7 @@ public class TelemetryController extends BaseController { @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, - (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor())); + (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor())); } @ApiOperation(value = "Get latest time-series value (getLatestTimeseries)", @@ -487,13 +487,15 @@ public class TelemetryController extends BaseController { @ApiParam(value = "A long value representing the end timestamp of removal time range in milliseconds.") @RequestParam(name = "endTs", required = false) Long endTs, @ApiParam(value = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.") - @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException { + @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted, + @ApiParam(value = "If the parameter is set to true, the latest telemetry can be removed, otherwise, in case that parameter is set to false the latest value will not removed.") + @RequestParam(name = "deleteLatest", required = false, defaultValue = "true") boolean deleteLatest) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); - return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted); + return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted, deleteLatest); } private DeferredResult deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys, - Long startTs, Long endTs, boolean rewriteLatestIfDeleted) throws ThingsboardException { + Long startTs, Long endTs, boolean rewriteLatestIfDeleted, boolean deleteLatest) throws ThingsboardException { List keys = toKeysList(keysStr); if (keys.isEmpty()) { return getImmediateDeferredResult("Empty keys: " + keysStr, HttpStatus.BAD_REQUEST); @@ -517,7 +519,7 @@ public class TelemetryController extends BaseController { return accessValidator.validateEntityAndCallback(user, Operation.WRITE_TELEMETRY, entityIdStr, (result, tenantId, entityId) -> { List deleteTsKvQueries = new ArrayList<>(); for (String key : keys) { - deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted)); + deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted, deleteLatest)); } tsSubService.deleteTimeseriesAndNotify(tenantId, entityId, keys, deleteTsKvQueries, new FutureCallback<>() { @Override @@ -535,6 +537,55 @@ public class TelemetryController extends BaseController { }); } + @ApiOperation(value = "Delete entity latest time-series data (deleteEntityLatestTimeseries)", + notes = "Delete latest time-series for selected entity based on entity id, entity type and keys. " + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + produces = MediaType.APPLICATION_JSON_VALUE) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Timeseries for the selected keys in the request was removed. " + + "Platform creates an audit log event about entity latest timeseries removal with action type 'TIMESERIES_DELETED'."), + @ApiResponse(code = 400, message = "Platform returns a bad request in case if keys list is empty."), + @ApiResponse(code = 401, message = "User is not authorized to delete entity latest timeseries for selected entity. Most likely, User belongs to different Customer or Tenant."), + @ApiResponse(code = 500, message = "The exception was thrown during processing the request. " + + "Platform creates an audit log event about entity latest timeseries removal with action type 'TIMESERIES_DELETED' that includes an error stacktrace."), + }) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/{entityType}/{entityId}/timeseries/latest/delete", method = RequestMethod.DELETE) + @ResponseBody + public DeferredResult deleteEntityLatestTimeseries(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") + @PathVariable("entityType") String entityType, + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) + @PathVariable("entityId") String entityIdStr, + @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION, required = true) + @RequestParam(name = "keys") String keysStr) throws ThingsboardException { + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); + return deleteLatestTimeseries(entityId, keysStr); + } + + private DeferredResult deleteLatestTimeseries(EntityId entityIdStr, String keysStr) throws ThingsboardException { + List keys = toKeysList(keysStr); + if (keys.isEmpty()) { + return getImmediateDeferredResult("Empty keys: " + keysStr, HttpStatus.BAD_REQUEST); + } + SecurityUser user = getCurrentUser(); + + return accessValidator.validateEntityAndCallback(user, Operation.WRITE_TELEMETRY, entityIdStr, (result, tenantId, entityId) -> + tsSubService.deleteLatestAndNotify(tenantId, entityId, keys, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable Void tmp) { + logLatestTimeseriesDeleted(user, entityId, keys, null); + result.setResult(new ResponseEntity<>(HttpStatus.OK)); + } + + @Override + public void onFailure(Throwable t) { + logLatestTimeseriesDeleted(user, entityId, keys, t); + result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); + } + }) + ); + } + @ApiOperation(value = "Delete device attributes (deleteDeviceAttributes)", notes = "Delete device attributes using provided Device Id, scope and a list of keys. " + "Referencing a non-existing Device Id will cause an error" + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, @@ -827,6 +878,11 @@ public class TelemetryController extends BaseController { toException(e), keys, startTs, endTs); } + private void logLatestTimeseriesDeleted(SecurityUser user, EntityId entityId, List keys, Throwable e) { + notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.TIMESERIES_DELETED, user, + toException(e), keys); + } + private void logTelemetryUpdated(SecurityUser user, EntityId entityId, List telemetry, Throwable e) { notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.TIMESERIES_UPDATED, user, toException(e), telemetry); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 3f5e52796a..40f4e3415b 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -316,6 +316,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer addWsCallback(deleteFuture, list -> onTimeSeriesDelete(tenantId, entityId, keys, list)); } + @Override + public void deleteLatestAndNotify(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback) { + ListenableFuture> deleteFuture = tsService.removeLatest(tenantId, entityId, keys); + addVoidCallback(deleteFuture, callback); + addWsCallback(deleteFuture, list -> onTimeSeriesDelete(tenantId, entityId, keys, list)); + } + @Override public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback callback) { saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value) 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 7f57fbbb3f..b448e4fdb8 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -792,6 +792,10 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass); } + protected T doDeleteAsync(String urlTemplate, Class responseClass, String... params) throws Exception { + return readResponse(doDeleteAsync(urlTemplate, DEFAULT_TIMEOUT, params).andExpect(status().isOk()), responseClass); + } + protected ResultActions doPost(String urlTemplate, String... params) throws Exception { MockHttpServletRequestBuilder postRequest = post(urlTemplate); setJwtToken(postRequest); @@ -824,6 +828,15 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { return mockMvc.perform(deleteRequest); } + protected ResultActions doDeleteAsync(String urlTemplate, Long timeout, String... params) throws Exception { + MockHttpServletRequestBuilder deleteRequest = delete(urlTemplate, params); + setJwtToken(deleteRequest); +// populateParams(deleteRequest, params); + MvcResult result = mockMvc.perform(deleteRequest).andReturn(); + result.getAsyncResult(timeout); + return mockMvc.perform(asyncDispatch(result)); + } + protected void populateParams(MockHttpServletRequestBuilder request, String... params) { if (params != null && params.length > 0) { Assert.assertEquals(0, params.length % 2); diff --git a/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java index 47cac1b549..2fdab098e4 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java @@ -15,14 +15,21 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Assert; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.SingleEntityFilter; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.service.DaoSqlTest; +import java.util.List; + import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.query.EntityKeyType.TIME_SERIES; @DaoSqlTest public class TelemetryControllerTest extends AbstractControllerTest { @@ -39,6 +46,136 @@ public class TelemetryControllerTest extends AbstractControllerTest { doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest()); } + @Test + public void testDeleteLatest() throws Exception { + loginTenantAdmin(); + Device device = createDevice(); + + SingleEntityFilter filter = new SingleEntityFilter(); + filter.setSingleEntity(device.getId()); + + getWsClient().subscribeLatestUpdate(List.of(new EntityKey(TIME_SERIES, "data")), filter); + + getWsClient().registerWaitForUpdate(1); + + long startTs = System.currentTimeMillis(); + + String testBody = "{\"data\": \"value\"}"; + doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", testBody, String.class, status().isOk()); + + long endTs = System.currentTimeMillis(); + + ObjectNode latest = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data", ObjectNode.class); + + Assert.assertNotNull(latest); + var data = latest.get("data"); + Assert.assertNotNull(data); + + Assert.assertEquals("value", data.get(0).get("value").asText()); + + ObjectNode timeseries = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data&startTs={startTs}&endTs={endTs}", ObjectNode.class, startTs, endTs); + + Assert.assertNotNull(timeseries); + + Assert.assertEquals("value", timeseries.get("data").get(0).get("value").asText()); + + doDeleteAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/latest/delete?keys=data", String.class); + + latest = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data", ObjectNode.class); + + Assert.assertTrue(latest.get("data").get(0).get("value").isNull()); + + timeseries = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data&startTs={startTs}&endTs={endTs}", ObjectNode.class, startTs, endTs); + + Assert.assertEquals("value", timeseries.get("data").get(0).get("value").asText()); + } + + @Test + public void testDeleteAllTelemetryWithLatest() throws Exception { + loginTenantAdmin(); + Device device = createDevice(); + + SingleEntityFilter filter = new SingleEntityFilter(); + filter.setSingleEntity(device.getId()); + + getWsClient().subscribeLatestUpdate(List.of(new EntityKey(TIME_SERIES, "data")), filter); + + getWsClient().registerWaitForUpdate(1); + + long startTs = System.currentTimeMillis(); + + String testBody = "{\"data\": \"value\"}"; + doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", testBody, String.class, status().isOk()); + + long endTs = System.currentTimeMillis(); + + ObjectNode latest = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data", ObjectNode.class); + + Assert.assertNotNull(latest); + var data = latest.get("data"); + Assert.assertNotNull(data); + + Assert.assertEquals("value", data.get(0).get("value").asText()); + + ObjectNode timeseries = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data&startTs={startTs}&endTs={endTs}", ObjectNode.class, startTs, endTs); + + Assert.assertNotNull(timeseries); + + Assert.assertEquals("value", timeseries.get("data").get(0).get("value").asText()); + + doDeleteAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/delete?keys=data&deleteAllDataForKeys=true", String.class); + + latest = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data", ObjectNode.class); + + Assert.assertTrue(latest.get("data").get(0).get("value").isNull()); + + timeseries = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data&startTs={startTs}&endTs={endTs}", ObjectNode.class, startTs, endTs); + + Assert.assertTrue(timeseries.isEmpty()); + } + + @Test + public void testDeleteAllTelemetryWithoutLatest() throws Exception { + loginTenantAdmin(); + Device device = createDevice(); + + SingleEntityFilter filter = new SingleEntityFilter(); + filter.setSingleEntity(device.getId()); + + getWsClient().subscribeLatestUpdate(List.of(new EntityKey(TIME_SERIES, "data")), filter); + + getWsClient().registerWaitForUpdate(1); + + long startTs = System.currentTimeMillis(); + + String testBody = "{\"data\": \"value\"}"; + doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", testBody, String.class, status().isOk()); + + long endTs = System.currentTimeMillis(); + + ObjectNode latest = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data", ObjectNode.class); + + Assert.assertNotNull(latest); + + Assert.assertEquals("value", latest.get("data").get(0).get("value").asText()); + + ObjectNode timeseries = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data&startTs={startTs}&endTs={endTs}", ObjectNode.class, startTs, endTs); + + Assert.assertNotNull(timeseries); + + Assert.assertEquals("value", timeseries.get("data").get(0).get("value").asText()); + + doDeleteAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/delete?keys=data&deleteAllDataForKeys=true&deleteLatest=false", String.class); + + latest = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data", ObjectNode.class); + + Assert.assertEquals("value", latest.get("data").get(0).get("value").asText()); + + timeseries = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data&startTs={startTs}&endTs={endTs}", ObjectNode.class, startTs, endTs); + + Assert.assertTrue(timeseries.isEmpty()); + } + private Device createDevice() throws Exception { String testToken = "TEST_TOKEN"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java index dee0e7aa9b..a97cc8f834 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java @@ -21,14 +21,20 @@ import lombok.Data; public class BaseDeleteTsKvQuery extends BaseTsKvQuery implements DeleteTsKvQuery { private final Boolean rewriteLatestIfDeleted; + private final Boolean deleteLatest; - public BaseDeleteTsKvQuery(String key, long startTs, long endTs, boolean rewriteLatestIfDeleted) { + public BaseDeleteTsKvQuery(String key, long startTs, long endTs, boolean rewriteLatestIfDeleted, boolean deleteLatest) { super(key, startTs, endTs); this.rewriteLatestIfDeleted = rewriteLatestIfDeleted; + this.deleteLatest = deleteLatest; + } + + public BaseDeleteTsKvQuery(String key, long startTs, long endTs, boolean rewriteLatestIfDeleted) { + this(key, startTs, endTs, rewriteLatestIfDeleted, true); } public BaseDeleteTsKvQuery(String key, long startTs, long endTs) { - this(key, startTs, endTs, false); + this(key, startTs, endTs, false, true); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java index 7b9b4ad16f..b2f41fffb4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java @@ -19,4 +19,6 @@ public interface DeleteTsKvQuery extends TsKvQuery { Boolean getRewriteLatestIfDeleted(); + Boolean getDeleteLatest(); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index 34bcd15c0a..6b8bfa9d64 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -275,7 +275,9 @@ public class BaseTimeseriesService implements TimeseriesService { private void deleteAndRegisterFutures(TenantId tenantId, List> futures, EntityId entityId, DeleteTsKvQuery query) { futures.add(Futures.transform(timeseriesDao.remove(tenantId, entityId, query), v -> null, MoreExecutors.directExecutor())); - futures.add(timeseriesLatestDao.removeLatest(tenantId, entityId, query)); + if (query.getDeleteLatest()) { + futures.add(timeseriesLatestDao.removeLatest(tenantId, entityId, query)); + } } private static void validate(EntityId entityId) { diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 51eb446d70..8c8829a727 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -2364,7 +2364,8 @@ public class RestClient implements Closeable { boolean deleteAllDataForKeys, Long startTs, Long endTs, - boolean rewriteLatestIfDeleted) { + boolean rewriteLatestIfDeleted, + boolean deleteLatest) { Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); @@ -2373,17 +2374,34 @@ public class RestClient implements Closeable { params.put("startTs", startTs.toString()); params.put("endTs", endTs.toString()); params.put("rewriteLatestIfDeleted", String.valueOf(rewriteLatestIfDeleted)); + params.put("deleteLatest", String.valueOf(deleteLatest)); return restTemplate .exchange( - baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/delete?keys={keys}&deleteAllDataForKeys={deleteAllDataForKeys}&startTs={startTs}&endTs={endTs}&rewriteLatestIfDeleted={rewriteLatestIfDeleted}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/delete?keys={keys}&deleteAllDataForKeys={deleteAllDataForKeys}&startTs={startTs}&endTs={endTs}&rewriteLatestIfDeleted={rewriteLatestIfDeleted}&deleteLatest={deleteLatest}", HttpMethod.DELETE, HttpEntity.EMPTY, Object.class, params) .getStatusCode() .is2xxSuccessful(); + } + public boolean deleteEntityLatestTimeseries(EntityId entityId, List keys) { + Map params = new HashMap<>(); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); + params.put("keys", listToString(keys)); + + return restTemplate + .exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/latest/delete?keys={keys}", + HttpMethod.DELETE, + HttpEntity.EMPTY, + Object.class, + params) + .getStatusCode() + .is2xxSuccessful(); } public boolean deleteEntityAttributes(DeviceId deviceId, String scope, List keys) { diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java index a61e83f48f..9acd03f665 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java @@ -71,4 +71,6 @@ public interface RuleEngineTelemetryService { void deleteAllLatest(TenantId tenantId, EntityId entityId, FutureCallback> callback); void deleteTimeseriesAndNotify(TenantId tenantId, EntityId entityId, List keys, List deleteTsKvQueries, FutureCallback callback); + + void deleteLatestAndNotify(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback); } From 2f1290e7e1be7b01c76b2f1a0da911fa03b171b6 Mon Sep 17 00:00:00 2001 From: Ruslan Vasylkiv <87172504+rusikv@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:24:45 +0300 Subject: [PATCH 05/51] Delete timeseries UI implementation (#8932) --- ui-ngx/src/app/core/http/attribute.service.ts | 14 ++- .../attribute/attribute-table.component.html | 12 +++ .../attribute/attribute-table.component.ts | 83 ++++++++++++++- .../delete-timeseries-panel.component.html | 74 +++++++++++++ .../delete-timeseries-panel.component.scss | 28 +++++ .../delete-timeseries-panel.component.ts | 100 ++++++++++++++++++ .../home/components/home-components.module.ts | 2 + .../models/telemetry/telemetry.models.ts | 18 +++- .../assets/locale/locale.constant-ca_ES.json | 2 +- .../assets/locale/locale.constant-cs_CZ.json | 2 +- .../assets/locale/locale.constant-da_DK.json | 2 +- .../assets/locale/locale.constant-de_DE.json | 2 +- .../assets/locale/locale.constant-el_GR.json | 2 +- .../assets/locale/locale.constant-en_US.json | 15 ++- .../assets/locale/locale.constant-es_ES.json | 2 +- .../assets/locale/locale.constant-fa_IR.json | 2 +- .../assets/locale/locale.constant-fr_FR.json | 2 +- .../assets/locale/locale.constant-it_IT.json | 2 +- .../assets/locale/locale.constant-ja_JP.json | 2 +- .../assets/locale/locale.constant-ka_GE.json | 2 +- .../assets/locale/locale.constant-ko_KR.json | 2 +- .../assets/locale/locale.constant-lv_LV.json | 2 +- .../assets/locale/locale.constant-pt_BR.json | 2 +- .../assets/locale/locale.constant-ro_RO.json | 2 +- .../assets/locale/locale.constant-sl_SI.json | 2 +- .../assets/locale/locale.constant-tr_TR.json | 2 +- .../assets/locale/locale.constant-uk_UA.json | 2 +- .../assets/locale/locale.constant-zh_CN.json | 2 +- .../assets/locale/locale.constant-zh_TW.json | 2 +- 29 files changed, 357 insertions(+), 29 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index 67132d8983..b772cd63e6 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -50,10 +50,11 @@ export class AttributeService { } public deleteEntityTimeseries(entityId: EntityId, timeseries: Array, deleteAllDataForKeys = false, - startTs?: number, endTs?: number, config?: RequestConfig): Observable { + startTs?: number, endTs?: number, rewriteLatestIfDeleted = false, deleteLatest = false, + config?: RequestConfig): Observable { const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(','); let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` + - `?keys=${keys}&deleteAllDataForKeys=${deleteAllDataForKeys}`; + `?keys=${keys}&deleteAllDataForKeys=${deleteAllDataForKeys}&rewriteLatestIfDeleted=${rewriteLatestIfDeleted}&deleteLatest=${deleteLatest}`; if (isDefinedAndNotNull(startTs)) { url += `&startTs=${startTs}`; } @@ -63,6 +64,12 @@ export class AttributeService { return this.http.delete(url, defaultHttpOptionsFromConfig(config)); } + public deleteEntityLatestTimeseries(entityId: EntityId, timeseries: Array, config?: RequestConfig): Observable { + const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(','); + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/latest/delete?keys=${keys}`; + return this.http.delete(url, defaultHttpOptionsFromConfig(config)); + } + public saveEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array, config?: RequestConfig): Observable { const attributesData: {[key: string]: any} = {}; @@ -103,7 +110,8 @@ export class AttributeService { }); let deleteEntityTimeseriesObservable: Observable; if (deleteTimeseries.length) { - deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, true, null, null, config); + deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, true, + null, null, false, false, config); } else { deleteEntityTimeseriesObservable = of(null); } diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index 32ebb28ae9..1def300b78 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -93,6 +93,14 @@ (click)="deleteAttributes($event)"> delete + diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts index e9d67a3fdd..13a7ef0a70 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts @@ -38,7 +38,7 @@ import { TranslateService } from '@ngx-translate/core'; import { MatDialog } from '@angular/material/dialog'; import { DialogService } from '@core/services/dialog.service'; import { Direction, SortOrder } from '@shared/models/page/sort-order'; -import { fromEvent, merge } from 'rxjs'; +import { fromEvent, merge, Observable } from 'rxjs'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; import { EntityId } from '@shared/models/id/entity-id'; import { @@ -48,7 +48,7 @@ import { isClientSideTelemetryType, LatestTelemetry, TelemetryType, - telemetryTypeTranslations, + telemetryTypeTranslations, TimeseriesDeleteStrategy, toTelemetryType } from '@shared/models/telemetry/telemetry.models'; import { AttributeDatasource } from '@home/models/datasource/attribute-datasource'; @@ -82,10 +82,14 @@ import { AddWidgetToDashboardDialogComponent, AddWidgetToDashboardDialogData } from '@home/components/attribute/add-widget-to-dashboard-dialog.component'; -import { deepClone } from '@core/utils'; +import { deepClone, isUndefinedOrNull } from '@core/utils'; import { Filters } from '@shared/models/query/query.models'; import { hidePageSizePixelValue } from '@shared/models/constants'; import { ResizeObserver } from '@juggle/resize-observer'; +import { + DELETE_TIMESERIES_PANEL_DATA, + DeleteTimeseriesPanelComponent, DeleteTimeseriesPanelData +} from '@home/components/attribute/delete-timeseries-panel.component'; @Component({ @@ -378,6 +382,79 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI }); } + deleteTimeseries($event: Event, attribute?: AttributeData) { + if ($event) { + $event.stopPropagation(); + } + const isMultipleDeletion = isUndefinedOrNull(attribute); + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'start', + originY: 'top', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + config.maxWidth = '488px'; + config.width = '100%'; + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + + const providers: StaticProvider[] = [ + { + provide: DELETE_TIMESERIES_PANEL_DATA, + useValue: { + isMultipleDeletion: isMultipleDeletion + } as DeleteTimeseriesPanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); + const componentRef = overlayRef.attach(new ComponentPortal(DeleteTimeseriesPanelComponent, + this.viewContainerRef, injector)); + componentRef.onDestroy(() => { + if (componentRef.instance.result !== null) { + const strategy = componentRef.instance.result; + const timeseries = isMultipleDeletion ? this.dataSource.selection.selected : [attribute]; + let deleteAllDataForKeys = false; + let rewriteLatestIfDeleted = false; + let startTs = null; + let endTs = null; + let deleteLatest = false; + let task: Observable; + if (strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY) { + deleteAllDataForKeys = true; + deleteLatest = true; + } + if (strategy === TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE) { + deleteAllDataForKeys = true; + } + if (strategy === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE) { + task = this.attributeService.deleteEntityLatestTimeseries(this.entityIdValue, timeseries); + } + if (strategy === TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD) { + startTs = componentRef.instance.startDateTime.getTime(); + endTs = componentRef.instance.endDateTime.getTime(); + rewriteLatestIfDeleted = componentRef.instance.rewriteLatestIfDeleted; + } + if (!task) { + task = this.attributeService.deleteEntityTimeseries(this.entityIdValue, timeseries, deleteAllDataForKeys, + startTs, endTs, rewriteLatestIfDeleted, deleteLatest); + } + task.subscribe(() => this.reloadAttributes()); + } + }); + } + deleteAttributes($event: Event) { if ($event) { $event.stopPropagation(); diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html new file mode 100644 index 0000000000..e164cb56ed --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html @@ -0,0 +1,74 @@ + + +
+ +

{{ "attribute.delete-timeseries.delete-strategy" | translate }}

+ + +
+
+ + attribute.delete-timeseries.strategy + + + {{ strategiesTranslationsMap.get(strategy) | translate }} + + + +
+
+ + attribute.delete-timeseries.start-time + + + + + + attribute.delete-timeseries.ends-on + + + + +
+ + {{ "attribute.delete-timeseries.rewrite-latest-value-if-deleted" | translate }} + +
+
+
+ + + +
+
+ diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss new file mode 100644 index 0000000000..c0f26644d5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2023 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 { + width: 100%; + background-color: #fff; + box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3), 0px 2px 6px 2px rgba(0, 0, 0, 0.15); + border-radius: 4px; +} + +:host ::ng-deep{ + div .mat-toolbar { + background: none; + } +} diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts new file mode 100644 index 0000000000..914e5246f7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts @@ -0,0 +1,100 @@ +/// +/// Copyright © 2016-2023 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, Inject, InjectionToken, OnInit } from '@angular/core'; +import { OverlayRef } from '@angular/cdk/overlay'; +import { + TimeseriesDeleteStrategy, + timeseriesDeleteStrategyTranslations +} from '@shared/models/telemetry/telemetry.models'; +import { MINUTE } from '@shared/models/time/time.models'; + +export const DELETE_TIMESERIES_PANEL_DATA = new InjectionToken('DeleteTimeseriesPanelData'); + +export interface DeleteTimeseriesPanelData { + isMultipleDeletion: boolean; +} + +@Component({ + selector: 'tb-delete-timeseries-panel', + templateUrl: './delete-timeseries-panel.component.html', + styleUrls: ['./delete-timeseries-panel.component.scss'] +}) +export class DeleteTimeseriesPanelComponent implements OnInit { + + strategy: string = TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY; + + result: string = null; + + startDateTime: Date; + + endDateTime: Date; + + rewriteLatestIfDeleted: boolean = false; + + strategiesTranslationsMap = timeseriesDeleteStrategyTranslations; + + multipleDeletionStrategies = [ + TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY, + TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE + ]; + + constructor(@Inject(DELETE_TIMESERIES_PANEL_DATA) public data: DeleteTimeseriesPanelData, + public overlayRef: OverlayRef) { } + + ngOnInit(): void { + let today = new Date(); + this.startDateTime = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()); + this.endDateTime = today; + if (this.data.isMultipleDeletion) { + this.strategiesTranslationsMap = new Map(Array.from(this.strategiesTranslationsMap.entries()) + .filter(([strategy]) => { + return this.multipleDeletionStrategies.includes(strategy); + })) + } + } + + delete(): void { + this.result = this.strategy; + this.overlayRef.dispose(); + } + + cancel(): void { + this.overlayRef.dispose(); + } + + isPeriodStrategy(): boolean { + return this.strategy === TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD; + } + + onStartDateTimeChange(newStartDateTime: Date) { + const endDateTimeTs = this.endDateTime.getTime(); + if (newStartDateTime.getTime() >= endDateTimeTs) { + this.startDateTime = new Date(endDateTimeTs - MINUTE); + } else { + this.startDateTime = newStartDateTime; + } + } + + onEndDateTimeChange(newEndDateTime: Date) { + const startDateTimeTs = this.startDateTime.getTime(); + if (newEndDateTime.getTime() <= startDateTimeTs) { + this.endDateTime = new Date(startDateTimeTs + MINUTE); + } else { + this.endDateTime = newEndDateTime; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index a6e2cc03dc..a18f785b35 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -177,6 +177,7 @@ import { } from '@home/components/widget/action/manage-widget-actions-dialog.component'; import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module'; import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module'; +import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component'; @NgModule({ declarations: @@ -205,6 +206,7 @@ import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/ba AttributeTableComponent, AddAttributeDialogComponent, EditAttributeValuePanelComponent, + DeleteTimeseriesPanelComponent, AliasesEntitySelectPanelComponent, AliasesEntitySelectComponent, AliasesEntityAutocompleteComponent, diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 50cbef2f8b..76f6b0f247 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -61,6 +61,13 @@ export enum TelemetryFeature { TIMESERIES = 'TIMESERIES' } +export enum TimeseriesDeleteStrategy { + DELETE_ALL_DATA_INCLUDING_KEY = 'DELETE_ALL_DATA_INCLUDING_KEY', + DELETE_OLD_DATA_EXCEPT_LATEST_VALUE = 'DELETE_OLD_DATA_EXCEPT_LATEST_VALUE', + DELETE_LATEST_VALUE = 'DELETE_LATEST_VALUE', + DELETE_DATA_FOR_TIME_PERIOD = 'DELETE_DATA_FOR_TIME_PERIOD' +} + export type TelemetryType = LatestTelemetry | AttributeScope; export const toTelemetryType = (val: string): TelemetryType => { @@ -73,7 +80,7 @@ export const toTelemetryType = (val: string): TelemetryType => { export const telemetryTypeTranslations = new Map( [ - [LatestTelemetry.LATEST_TELEMETRY, 'attribute.scope-latest-telemetry'], + [LatestTelemetry.LATEST_TELEMETRY, 'attribute.scope-telemetry'], [AttributeScope.CLIENT_SCOPE, 'attribute.scope-client'], [AttributeScope.SERVER_SCOPE, 'attribute.scope-server'], [AttributeScope.SHARED_SCOPE, 'attribute.scope-shared'] @@ -89,6 +96,15 @@ export const isClientSideTelemetryType = new Map( ] ); +export const timeseriesDeleteStrategyTranslations = new Map( + [ + [TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY, 'attribute.delete-timeseries.all-data-including-key'], + [TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE, 'attribute.delete-timeseries.old-data-except-latest'], + [TimeseriesDeleteStrategy.DELETE_LATEST_VALUE, 'attribute.delete-timeseries.latest-value'], + [TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD, 'attribute.delete-timeseries.data-for-time-period'] + ] +) + export interface AttributeData { lastUpdateTs?: number; key: string; diff --git a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json index 349d13da2c..edb406d9cb 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json @@ -632,7 +632,7 @@ "attributes": "Atributs", "latest-telemetry": "Última telemetria", "attributes-scope": "Abast dels atributs del dispositiu", - "scope-latest-telemetry": "Última telemetria", + "scope-telemetry": "Telemetria", "scope-client": "Atributs del Client", "scope-server": "Atributs del Servidor", "scope-shared": "Atributs Compartits", diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 52873b4d70..5697486e37 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -445,7 +445,7 @@ "attributes": "Atributy", "latest-telemetry": "Poslední telemetrie", "attributes-scope": "Rozsah atributů entity", - "scope-latest-telemetry": "Poslední telemetrie", + "scope-telemetry": "Telemetrie", "scope-client": "Atributy klienta", "scope-server": "Atributy serveru", "scope-shared": "Sdílené atributy", diff --git a/ui-ngx/src/assets/locale/locale.constant-da_DK.json b/ui-ngx/src/assets/locale/locale.constant-da_DK.json index 2c1df70902..1486870a7a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-da_DK.json +++ b/ui-ngx/src/assets/locale/locale.constant-da_DK.json @@ -453,7 +453,7 @@ "attributes": "Attributter", "latest-telemetry": "Seneste telemetri", "attributes-scope": "Omfang af entitetsattributter", - "scope-latest-telemetry": "Seneste telemetri", + "scope-telemetry": "Telemetri", "scope-client": "Klientattributter", "scope-server": "Serverattributter", "scope-shared": "Delte attributter", diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json index ed73ad25cf..c27d63f4fb 100644 --- a/ui-ngx/src/assets/locale/locale.constant-de_DE.json +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -324,7 +324,7 @@ "attributes": "Eigenschaften", "latest-telemetry": "Neueste Telemetrie", "attributes-scope": "Entitätseigenschaftsbereich", - "scope-latest-telemetry": "Neueste Telemetrie", + "scope-telemetry": "Telemetrie", "scope-client": "Client Eigenschaften", "scope-server": "Server Eigenschaften", "scope-shared": "Gemeinsame Eigenschaften", diff --git a/ui-ngx/src/assets/locale/locale.constant-el_GR.json b/ui-ngx/src/assets/locale/locale.constant-el_GR.json index 453b5dd83c..36e8e4e000 100644 --- a/ui-ngx/src/assets/locale/locale.constant-el_GR.json +++ b/ui-ngx/src/assets/locale/locale.constant-el_GR.json @@ -291,7 +291,7 @@ "attributes": "Χαρακτηριστικά", "latest-telemetry": "Τελευταία τηλεμετρία", "attributes-scope": "Πεδίο εφαρμογής Χαρακτηριστικών Οντότητας", - "scope-latest-telemetry": "Τελευταία τηλεμετρία", + "scope-telemetry": "Τηλεμετρία", "scope-client": "Χαρακτηριστικά Client", "scope-server": "Χαρακτηριστικά Server", "scope-shared": "Κοινόχρηστα Χαρακτηριστικά", 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 82f10b7f4c..091ee1e3bf 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -691,7 +691,7 @@ "attributes": "Attributes", "latest-telemetry": "Latest telemetry", "attributes-scope": "Entity attributes scope", - "scope-latest-telemetry": "Latest telemetry", + "scope-telemetry": "Telemetry", "scope-client": "Client attributes", "scope-server": "Server attributes", "scope-shared": "Shared attributes", @@ -717,7 +717,18 @@ "no-attributes-text": "No attributes found", "no-telemetry-text": "No telemetry found", "copy-key": "Copy key", - "copy-value": "Copy value" + "copy-value": "Copy value", + "delete-timeseries": { + "start-time": "Start time", + "ends-on": "Ends on", + "strategy": "Strategy", + "delete-strategy": "Delete strategy", + "all-data-including-key": "Delete all data including key", + "old-data-except-latest": "Delete old data except latest value", + "latest-value": "Delete latest value", + "data-for-time-period": "Delete data for time period", + "rewrite-latest-value-if-deleted": "Rewrite latest value if deleted" + } }, "api-usage": { "api-features": "API features", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index 6518e03f58..62152e998d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -667,7 +667,7 @@ "attributes": "Atributos", "latest-telemetry": "Última telemetría", "attributes-scope": "Alcance de los atributos del dispositivo", - "scope-latest-telemetry": "Última telemetría", + "scope-telemetry": "Telemetría", "scope-client": "Atributos de Cliente", "scope-server": "Atributos de Servidor", "scope-shared": "Atributos Compartidos", diff --git a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json index 6e5026b011..da841a6e53 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json @@ -254,7 +254,7 @@ "attributes": "ويژگي ها", "latest-telemetry": "آخرين سنجش", "attributes-scope": "حوزه ويژگي هاي موجودي", - "scope-latest-telemetry": "آخرين سنجش", + "scope-telemetry": "تله متری", "scope-client": "ويژگي هاي مشتري", "scope-server": "ويژگي هاي سِروِر", "scope-shared": "ويژگي هاي مشترک", diff --git a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json index 19f92e5a7c..a929477d5e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -459,7 +459,7 @@ "next-widget": "Widget suivant", "prev-widget": "Widget précédent", "scope-client": "Attributs du client", - "scope-latest-telemetry": "Dernière télémétrie", + "scope-telemetry": "Télémétrie", "scope-server": "Attributs du serveur", "scope-shared": "Attributs partagés", "selected-attributes": "{count, plural, =1 {1 attribut} other {# attributs} } sélectionnés", diff --git a/ui-ngx/src/assets/locale/locale.constant-it_IT.json b/ui-ngx/src/assets/locale/locale.constant-it_IT.json index 94acd17f82..2c093e76a9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-it_IT.json +++ b/ui-ngx/src/assets/locale/locale.constant-it_IT.json @@ -276,7 +276,7 @@ "attributes": "Attributi", "latest-telemetry": "Ultima telemetria", "attributes-scope": "Visibilità attributi entità", - "scope-latest-telemetry": "Ultima telemetria", + "scope-telemetry": "Telemetria", "scope-client": "Attributi client", "scope-server": "Attributi server", "scope-shared": "Attributi condivisi", diff --git a/ui-ngx/src/assets/locale/locale.constant-ja_JP.json b/ui-ngx/src/assets/locale/locale.constant-ja_JP.json index f63a6681a2..23145c1f89 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ja_JP.json +++ b/ui-ngx/src/assets/locale/locale.constant-ja_JP.json @@ -244,7 +244,7 @@ "attributes": "属性", "latest-telemetry": "最新テレメトリ", "attributes-scope": "エンティティ属性のスコープ", - "scope-latest-telemetry": "最新テレメトリ", + "scope-telemetry": "テレメトリー", "scope-client": "クライアントの属性", "scope-server": "サーバーの属性", "scope-shared": "共有属性", diff --git a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json index a6c5a6576d..89d25703e3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json +++ b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json @@ -290,7 +290,7 @@ "attributes": "ატრიბუტები", "latest-telemetry": "უახლესი ტელემეტრია", "attributes-scope": "ობიექტის ატრიბუტების ფარგლები", - "scope-latest-telemetry": "უახლესი ტელემეტრია", + "scope-telemetry": "ტელემეტრია", "scope-client": "კლიენტის ატრიბუტები", "scope-server": "სერვერის ატრიბუტები", "scope-shared": "ატრიბუტების გაზიარება", diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json index 758482f578..3da051ec1a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -408,7 +408,7 @@ "attributes": "속성", "latest-telemetry": "최근 데이터", "attributes-scope": "장치 속성 범위", - "scope-latest-telemetry": "최근 데이터", + "scope-telemetry": "원격 측정", "scope-client": "클라이언트 속성", "scope-server": "서버 속성", "scope-shared": "공유 속성", diff --git a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json index d584f2da96..f4f5befc14 100644 --- a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json +++ b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json @@ -256,7 +256,7 @@ "attributes": "Attribūti", "latest-telemetry": "Jaunākā telemetrija", "attributes-scope": "Vienības atribūtu darbības joma", - "scope-latest-telemetry": "Jaunākā telemetrija", + "scope-telemetry": "Telemetrija", "scope-client": "Klientu atribūti", "scope-server": "Servera atribūti", "scope-shared": "Dalītie atribūti", diff --git a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json index 2bba0338d2..28c7082df9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json +++ b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json @@ -309,7 +309,7 @@ "attributes": "Atributos", "latest-telemetry": "Última telemetria", "attributes-scope": "Escopo de atributos de entidade", - "scope-latest-telemetry": "Última telemetria", + "scope-telemetry": "Telemetria", "scope-client": "Atributos do cliente", "scope-server": "Atributos do servidor", "scope-shared": "Atributos compartilhados", diff --git a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json index fa5cee48c9..da0012e6f6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json +++ b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json @@ -285,7 +285,7 @@ "attributes": "Atribute", "latest-telemetry": "Ultimele Date Telemetrice", "attributes-scope": "Scop Atribute Entitate", - "scope-latest-telemetry": "Ultimele Date Telemetrice", + "scope-telemetry": "Telemetrie", "scope-client": "Atribute Client", "scope-server": "Atribute Server", "scope-shared": "Atribute Partajate", diff --git a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json index 8aced0ddc6..e53e4fc7c0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json +++ b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json @@ -408,7 +408,7 @@ "attributes": "Lastnosti", "latest-telemetry": "Najnovejša telemetrija", "attributes-scope": "Obseg atributov entitete", - "scope-latest-telemetry": "Najnovejša telemetrija", + "scope-telemetry": "Telemetrija", "scope-client": "Atributi odjemalca", "scope-server": "Atributi strežnika", "scope-shared": "Skupni atributi", diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json index b175a2d51a..ae79f0c81c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -445,7 +445,7 @@ "attributes": "Öznitelikler", "latest-telemetry": "Son telemetri", "attributes-scope": "Varlık öznitelik kapsamı", - "scope-latest-telemetry": "Son telemetri", + "scope-telemetry": "telemetri", "scope-client": "İstemci öznitelikler", "scope-server": "Sunucu öznitelikler", "scope-shared": "Paylaşılan öznitelikler", diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index 7aa541e57f..bd608cd709 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -342,7 +342,7 @@ "attributes": "Атрибути", "latest-telemetry": "Остання телеметрія", "attributes-scope": "Область видимості атрибутів", - "scope-latest-telemetry": "Остання телеметрія", + "scope-telemetry": "Телеметрія", "scope-client": "Клієнтські атрибути", "scope-server": "Серверні атрибути", "scope-shared": "Спільні атрибути", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index b39e10e46c..4c33ac46a2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -590,7 +590,7 @@ "attributes": "属性", "latest-telemetry": "最新遥测数据", "attributes-scope": "设备属性范围", - "scope-latest-telemetry": "最新遥测数据", + "scope-telemetry": "遥测", "scope-client": "客户端属性", "scope-server": "服务端属性", "scope-shared": "共享属性", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index f2cce81824..3a1c4edd83 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -519,7 +519,7 @@ "attributes": "屬性", "latest-telemetry": "最新遙測", "attributes-scope": "設備屬性範圍", - "scope-latest-telemetry": "最新遙測", + "scope-telemetry": "遙測", "scope-client": "客戶端屬性", "scope-server": "服務端屬性", "scope-shared": "共享屬性", From 32c2d44b0cd664aa832c4471c976690208dfa2be Mon Sep 17 00:00:00 2001 From: Ruslan Vasylkiv <87172504+rusikv@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:37:40 +0300 Subject: [PATCH 06/51] added rewrite param to delete latest timeseries, enabled single selection deletion (#8933) --- ui-ngx/src/app/core/http/attribute.service.ts | 10 ++++++---- .../attribute/attribute-table.component.html | 2 +- .../components/attribute/attribute-table.component.ts | 11 ++++++----- .../attribute/delete-timeseries-panel.component.html | 2 ++ .../attribute/delete-timeseries-panel.component.ts | 6 +++++- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index b772cd63e6..cc20069e04 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -50,7 +50,7 @@ export class AttributeService { } public deleteEntityTimeseries(entityId: EntityId, timeseries: Array, deleteAllDataForKeys = false, - startTs?: number, endTs?: number, rewriteLatestIfDeleted = false, deleteLatest = false, + startTs?: number, endTs?: number, rewriteLatestIfDeleted = false, deleteLatest = true, config?: RequestConfig): Observable { const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(','); let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` + @@ -64,9 +64,11 @@ export class AttributeService { return this.http.delete(url, defaultHttpOptionsFromConfig(config)); } - public deleteEntityLatestTimeseries(entityId: EntityId, timeseries: Array, config?: RequestConfig): Observable { + public deleteEntityLatestTimeseries(entityId: EntityId, timeseries: Array, rewrite = true, + config?: RequestConfig): Observable { const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(','); - let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/latest/delete?keys=${keys}`; + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/latest/delete?keys=${keys}` + + `$rewrite=${rewrite}`; return this.http.delete(url, defaultHttpOptionsFromConfig(config)); } @@ -111,7 +113,7 @@ export class AttributeService { let deleteEntityTimeseriesObservable: Observable; if (deleteTimeseries.length) { deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, true, - null, null, false, false, config); + null, null, false, true, config); } else { deleteEntityTimeseriesObservable = of(null); } diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index 1def300b78..95ade10645 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -93,7 +93,7 @@ (click)="deleteAttributes($event)"> delete - - - + diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss index c0f26644d5..d223b29b47 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss @@ -22,7 +22,7 @@ } :host ::ng-deep{ - div .mat-toolbar { + form .mat-toolbar { background: none; } } diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts index 364ecd2c9a..96eaa5aa1b 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts @@ -14,13 +14,15 @@ /// limitations under the License. /// -import { Component, Inject, InjectionToken, OnInit } from '@angular/core'; +import { Component, Inject, InjectionToken, OnDestroy, OnInit } from '@angular/core'; import { OverlayRef } from '@angular/cdk/overlay'; import { TimeseriesDeleteStrategy, timeseriesDeleteStrategyTranslations } from '@shared/models/telemetry/telemetry.models'; import { MINUTE } from '@shared/models/time/time.models'; +import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Subscription } from 'rxjs'; export const DELETE_TIMESERIES_PANEL_DATA = new InjectionToken('DeleteTimeseriesPanelData'); @@ -33,17 +35,15 @@ export interface DeleteTimeseriesPanelData { templateUrl: './delete-timeseries-panel.component.html', styleUrls: ['./delete-timeseries-panel.component.scss'] }) -export class DeleteTimeseriesPanelComponent implements OnInit { +export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { - strategy: string = TimeseriesDeleteStrategy.DELETE_ALL_DATA; + deleteTimeseriesFormGroup: UntypedFormGroup; - result: string = null; - - startDateTime: Date; + startDateTimeSubscription: Subscription; - endDateTime: Date; + endDateTimeSubscription: Subscription; - rewriteLatestIfDeleted: boolean = true; + result: string = null; strategiesTranslationsMap = timeseriesDeleteStrategyTranslations; @@ -53,22 +53,40 @@ export class DeleteTimeseriesPanelComponent implements OnInit { ]; constructor(@Inject(DELETE_TIMESERIES_PANEL_DATA) public data: DeleteTimeseriesPanelData, - public overlayRef: OverlayRef) { } + public overlayRef: OverlayRef, + public fb: UntypedFormBuilder) { } ngOnInit(): void { - let today = new Date(); - this.startDateTime = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()); - this.endDateTime = today; + const today = new Date(); if (this.data.isMultipleDeletion) { this.strategiesTranslationsMap = new Map(Array.from(this.strategiesTranslationsMap.entries()) .filter(([strategy]) => { return this.multipleDeletionStrategies.includes(strategy); })) } + this.deleteTimeseriesFormGroup = this.fb.group({ + strategy: [TimeseriesDeleteStrategy.DELETE_ALL_DATA], + startDateTime: [new Date(today.getFullYear(), today.getMonth() - 1, today.getDate())], + endDateTime: [today], + rewriteLatest: [true] + }) + this.startDateTimeSubscription = this.getStartDateTimeFormControl().valueChanges.subscribe( + value => this.onStartDateTimeChange(value) + ) + this.endDateTimeSubscription = this.getEndDateTimeFormControl().valueChanges.subscribe( + value => this.onEndDateTimeChange(value) + ) + } + + ngOnDestroy(): void { + this.startDateTimeSubscription.unsubscribe(); + this.startDateTimeSubscription = null; + this.endDateTimeSubscription.unsubscribe(); + this.endDateTimeSubscription = null; } delete(): void { - this.result = this.strategy; + this.result = this.getStrategyFormControl().value; this.overlayRef.dispose(); } @@ -77,28 +95,50 @@ export class DeleteTimeseriesPanelComponent implements OnInit { } isPeriodStrategy(): boolean { - return this.strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD; + return this.getStrategyFormControl().value === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD; } isDeleteLatestStrategy(): boolean { - return this.strategy === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE; + return this.getStrategyFormControl().value === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE; + } + + getStrategyFormControl(): AbstractControl { + return this.deleteTimeseriesFormGroup.get('strategy'); + } + + getStartDateTimeFormControl(): AbstractControl { + return this.deleteTimeseriesFormGroup.get('startDateTime'); + } + + getEndDateTimeFormControl(): AbstractControl { + return this.deleteTimeseriesFormGroup.get('endDateTime'); + } + + getRewriteLatestFormControl(): AbstractControl { + return this.deleteTimeseriesFormGroup.get('rewriteLatest'); } onStartDateTimeChange(newStartDateTime: Date) { - const endDateTimeTs = this.endDateTime.getTime(); - if (newStartDateTime.getTime() >= endDateTimeTs) { - this.startDateTime = new Date(endDateTimeTs - MINUTE); - } else { - this.startDateTime = newStartDateTime; + if (newStartDateTime) { + const endDateTimeTs = this.deleteTimeseriesFormGroup.get('endDateTime').value.getTime(); + const startDateTimeControl = this.getStartDateTimeFormControl(); + if (newStartDateTime.getTime() >= endDateTimeTs) { + startDateTimeControl.patchValue(new Date(endDateTimeTs - MINUTE), {onlySelf: true, emitEvent: false}); + } else { + startDateTimeControl.patchValue(newStartDateTime, {onlySelf: true, emitEvent: false}); + } } } onEndDateTimeChange(newEndDateTime: Date) { - const startDateTimeTs = this.startDateTime.getTime(); - if (newEndDateTime.getTime() <= startDateTimeTs) { - this.endDateTime = new Date(startDateTimeTs + MINUTE); - } else { - this.endDateTime = newEndDateTime; + if (newEndDateTime) { + const startDateTimeTs = this.deleteTimeseriesFormGroup.get('startDateTime').value.getTime(); + const endDateTimeControl = this.getEndDateTimeFormControl(); + if (newEndDateTime.getTime() <= startDateTimeTs) { + endDateTimeControl.patchValue(new Date(startDateTimeTs + MINUTE), {onlySelf: true, emitEvent: false}); + } else { + endDateTimeControl.patchValue(newEndDateTime, {onlySelf: true, emitEvent: false}); + } } } } From 2056434bb70e582eeffaf1600dd334004044334a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 2 Aug 2023 11:35:15 +0200 Subject: [PATCH 23/51] tests improvements --- .../state/DefaultDeviceStateServiceTest.java | 197 ++++++++++++++---- 1 file changed, 159 insertions(+), 38 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java index 631a82f518..ca30f9e5ed 100644 --- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java @@ -24,7 +24,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -41,14 +40,13 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.sql.query.EntityQueryRepository; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.discovery.PartitionService; -import org.thingsboard.server.service.partition.AbstractPartitionBasedService; +import org.thingsboard.server.queue.discovery.QueueKey; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; -import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.UUID; import static org.hamcrest.CoreMatchers.is; @@ -78,13 +76,29 @@ public class DefaultDeviceStateServiceTest { @Mock EntityQueryRepository entityQueryRepository; + TenantId tenantId = new TenantId(UUID.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112")); DeviceId deviceId = DeviceId.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112"); + TopicPartitionInfo tpi; DefaultDeviceStateService service; + TelemetrySubscriptionService telemetrySubscriptionService; + @Before public void setUp() { service = spy(new DefaultDeviceStateService(deviceService, attributesService, tsService, clusterService, partitionService, entityQueryRepository, null, null, mock(NotificationRuleProcessor.class))); + telemetrySubscriptionService = Mockito.mock(TelemetrySubscriptionService.class); + ReflectionTestUtils.setField(service, "tsSubService", telemetrySubscriptionService); + ReflectionTestUtils.setField(service, "defaultStateCheckIntervalInSec", 60); + ReflectionTestUtils.setField(service, "defaultActivityStatsIntervalInSec", 60); + ReflectionTestUtils.setField(service, "initFetchPackSize", 10); + + tpi = TopicPartitionInfo.builder().myPartition(true).build(); + Mockito.when(partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId)).thenReturn(tpi); + Mockito.when(entityQueryRepository.findEntityDataByQueryInternal(Mockito.any())).thenReturn(new PageData<>()); + var deviceIdInfo = new DeviceIdInfo(tenantId.getId(), null, deviceId.getId()); + Mockito.when(deviceService.findDeviceIdInfos(Mockito.any())) + .thenReturn(new PageData<>(List.of(deviceIdInfo), 0, 1, false)); } @Test @@ -140,35 +154,62 @@ public class DefaultDeviceStateServiceTest { Assert.assertEquals(5000L, deviceStateData.getState().getInactivityTimeout()); } + private void initStateService(long timeout) throws InterruptedException { + service.stop(); + Mockito.reset(service, telemetrySubscriptionService); + ReflectionTestUtils.setField(service, "defaultInactivityTimeoutMs", timeout); + service.init(); + PartitionChangeEvent event = new PartitionChangeEvent(this, new QueueKey(ServiceType.TB_CORE), Collections.singleton(tpi)); + service.onApplicationEvent(event); + Thread.sleep(100); + } + @Test - public void givenIncreaseInactivityTimeoutAndThenStateIsActive() throws Exception { - TelemetrySubscriptionService telemetrySubscriptionService = Mockito.mock(TelemetrySubscriptionService.class); - ReflectionTestUtils.setField(service, "tsSubService", telemetrySubscriptionService); - ReflectionTestUtils.setField(service, "defaultStateCheckIntervalInSec", 60); - ReflectionTestUtils.setField(service, "defaultActivityStatsIntervalInSec", 60); - ReflectionTestUtils.setField(service, "defaultInactivityTimeoutMs", 1); - ReflectionTestUtils.setField(service, "initFetchPackSize", 10); + public void increaseInactivityForInactiveDeviceTest() throws Exception { + final long defaultTimeout = 1; + initStateService(defaultTimeout); + DeviceState deviceState = DeviceState.builder().build(); + DeviceStateData deviceStateData = DeviceStateData.builder() + .tenantId(tenantId) + .deviceId(deviceId) + .state(deviceState) + .metaData(new TbMsgMetaData()) + .build(); - Mockito.when(entityQueryRepository.findEntityDataByQueryInternal(Mockito.any())).thenReturn(new PageData<>()); + service.deviceStates.put(deviceId, deviceStateData); + service.getPartitionedEntities(tpi).add(deviceId); - service.init(); - var tenantId = new TenantId(UUID.randomUUID()); - var tpi = TopicPartitionInfo.builder().myPartition(true).build(); - Mockito.when(partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId)).thenReturn(tpi); + service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); + activityVerify(true); + Thread.sleep(defaultTimeout); + service.checkStates(); + activityVerify(false); - var deviceIdInfo = new DeviceIdInfo(tenantId.getId(), null, deviceId.getId()); + Mockito.reset(telemetrySubscriptionService); - Mockito.when(deviceService.findDeviceIdInfos(Mockito.any())) - .thenReturn(new PageData<>(List.of(deviceIdInfo), 0, 1, false)); + long increase = 100; + long newTimeout = System.currentTimeMillis() - deviceState.getLastActivityTime() + increase; + + service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); + activityVerify(true); + Thread.sleep(increase); + service.checkStates(); + activityVerify(false); - Method method = AbstractPartitionBasedService.class.getDeclaredMethod("initStateFromDB", Set.class); - method.setAccessible(true); - method.invoke(service, Collections.singleton(tpi)); + Mockito.reset(telemetrySubscriptionService); - service.onAddedPartitions(Collections.singleton(tpi)); + service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); + activityVerify(true); + Thread.sleep(newTimeout + 5); + service.checkStates(); + activityVerify(false); + } + @Test + public void increaseInactivityForActiveDeviceTest() throws Exception { + final long defaultTimeout = 1000; + initStateService(defaultTimeout); DeviceState deviceState = DeviceState.builder().build(); - DeviceStateData deviceStateData = DeviceStateData.builder() .tenantId(tenantId) .deviceId(deviceId) @@ -177,44 +218,124 @@ public class DefaultDeviceStateServiceTest { .build(); service.deviceStates.put(deviceId, deviceStateData); - service.getPartitionedEntities(tpi).add(deviceId); service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); + activityVerify(true); - Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(true), Mockito.any()); + Mockito.reset(telemetrySubscriptionService); - Thread.sleep(1); + long increase = 100; + long newTimeout = System.currentTimeMillis() - deviceState.getLastActivityTime() + increase; + service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); + Mockito.verify(telemetrySubscriptionService, Mockito.never()).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.any(), Mockito.any()); + Thread.sleep(defaultTimeout + increase); service.checkStates(); - - Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(false), Mockito.any()); + activityVerify(false); Mockito.reset(telemetrySubscriptionService); - service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, System.currentTimeMillis() - deviceState.getLastActivityTime() + 1000); + service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); + activityVerify(true); + Thread.sleep(newTimeout); + service.checkStates(); + activityVerify(false); + } - Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(true), Mockito.any()); + @Test + public void increaseSmallInactivityForInactiveDeviceTest() throws Exception { + final long defaultTimeout = 1; + initStateService(defaultTimeout); + DeviceState deviceState = DeviceState.builder().build(); + DeviceStateData deviceStateData = DeviceStateData.builder() + .tenantId(tenantId) + .deviceId(deviceId) + .state(deviceState) + .metaData(new TbMsgMetaData()) + .build(); - Thread.sleep(2000); + service.deviceStates.put(deviceId, deviceStateData); + service.getPartitionedEntities(tpi).add(deviceId); + service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); + activityVerify(true); + Thread.sleep(defaultTimeout); service.checkStates(); + activityVerify(false); - Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(false), Mockito.any()); + Mockito.reset(telemetrySubscriptionService); + long newTimeout = 1; + Thread.sleep(newTimeout); + Mockito.verify(telemetrySubscriptionService, Mockito.never()).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.any(), Mockito.any()); + } + + @Test + public void decreaseInactivityForActiveDeviceTest() throws Exception { + final long defaultTimeout = 1000; + initStateService(defaultTimeout); + DeviceState deviceState = DeviceState.builder().build(); + DeviceStateData deviceStateData = DeviceStateData.builder() + .tenantId(tenantId) + .deviceId(deviceId) + .state(deviceState) + .metaData(new TbMsgMetaData()) + .build(); + + service.deviceStates.put(deviceId, deviceStateData); + service.getPartitionedEntities(tpi).add(deviceId); + + service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); + activityVerify(true); + + Mockito.reset(telemetrySubscriptionService); + + Mockito.verify(telemetrySubscriptionService, Mockito.never()).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.any(), Mockito.any()); + + long newTimeout = 1; + + service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); + activityVerify(false); Mockito.reset(telemetrySubscriptionService); - service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, 2000); + service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, defaultTimeout); + activityVerify(true); + Thread.sleep(defaultTimeout); + service.checkStates(); + activityVerify(false); + } + + @Test + public void decreaseInactivityForInactiveDeviceTest() throws Exception { + final long defaultTimeout = 1000; + initStateService(defaultTimeout); + DeviceState deviceState = DeviceState.builder().build(); + DeviceStateData deviceStateData = DeviceStateData.builder() + .tenantId(tenantId) + .deviceId(deviceId) + .state(deviceState) + .metaData(new TbMsgMetaData()) + .build(); - Mockito.verify(telemetrySubscriptionService, Mockito.never()).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(true), Mockito.any()); + service.deviceStates.put(deviceId, deviceStateData); + service.getPartitionedEntities(tpi).add(deviceId); service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); + activityVerify(true); + Thread.sleep(defaultTimeout); + service.checkStates(); + activityVerify(false); + Mockito.reset(telemetrySubscriptionService); - Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(true), Mockito.any()); + long newTimeout = 1; - service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, 1); + service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); + Mockito.verify(telemetrySubscriptionService, Mockito.never()).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.any(), Mockito.any()); + } - Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(false), Mockito.any()); + private void activityVerify(boolean isActive) { + Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(isActive), Mockito.any()); } } \ No newline at end of file From 12c8903ff559ed96ab741a0335e860cc3c62381f Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 2 Aug 2023 12:48:19 +0300 Subject: [PATCH 24/51] Delete timeseries panel form is builded by FormBuilder and is now FormGroup, result of panel is now in result field --- .../attribute/attribute-table.component.html | 2 +- .../attribute/attribute-table.component.ts | 19 ++++---- .../delete-timeseries-panel.component.ts | 44 ++++++++++--------- 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index 788aeeaabc..10a5b03dba 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -85,7 +85,7 @@ 'attribute.selected-telemetry' : 'attribute.selected-attributes') | translate:{count: dataSource.selection.selected.length} }} - @@ -41,13 +41,13 @@ attribute.delete-timeseries.start-time - + attribute.delete-timeseries.ends-on - + @@ -57,18 +57,18 @@ -
- - - -
- +
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts index 04db1ee223..8adb9bcd5a 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts @@ -21,8 +21,8 @@ import { timeseriesDeleteStrategyTranslations } from '@shared/models/telemetry/telemetry.models'; import { MINUTE } from '@shared/models/time/time.models'; -import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms'; -import { Subject, Subscription } from 'rxjs'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; export const DELETE_TIMESERIES_PANEL_DATA = new InjectionToken('DeleteTimeseriesPanelData'); @@ -47,10 +47,6 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { deleteTimeseriesFormGroup: FormGroup; - startDateTimeSubscription: Subscription; - - endDateTimeSubscription: Subscription; - result: DeleteTimeseriesPanelResult = null; strategiesTranslationsMap = timeseriesDeleteStrategyTranslations; @@ -76,14 +72,28 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { } this.deleteTimeseriesFormGroup = this.fb.group({ strategy: [TimeseriesDeleteStrategy.DELETE_ALL_DATA], - startDateTime: [new Date(today.getFullYear(), today.getMonth() - 1, today.getDate())], - endDateTime: [today], + startDateTime: [ + { value: new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()), disabled: true }, + [Validators.required] + ], + endDateTime: [{ value: today, disabled: true }, [Validators.required]], rewriteLatest: [true] }) - this.startDateTimeSubscription = this.getStartDateTimeFormControl().valueChanges.pipe( + this.deleteTimeseriesFormGroup.get('strategy').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(value => { + if (value === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD) { + this.deleteTimeseriesFormGroup.get('startDateTime').enable({onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('endDateTime').enable({onlySelf: true, emitEvent: false}); + } else { + this.deleteTimeseriesFormGroup.get('startDateTime').disable({onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('endDateTime').disable({onlySelf: true, emitEvent: false}); + } + }) + this.deleteTimeseriesFormGroup.get('startDateTime').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe(value => this.onStartDateTimeChange(value)); - this.endDateTimeSubscription = this.getEndDateTimeFormControl().valueChanges.pipe( + this.deleteTimeseriesFormGroup.get('endDateTime').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe(value => this.onEndDateTimeChange(value)); } @@ -94,8 +104,12 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { } delete(): void { - this.result = this.deleteTimeseriesFormGroup.value; - this.overlayRef.dispose(); + if (this.deleteTimeseriesFormGroup.valid) { + this.result = this.deleteTimeseriesFormGroup.value; + this.overlayRef.dispose(); + } else { + this.deleteTimeseriesFormGroup.markAllAsTouched(); + } } cancel(): void { @@ -103,33 +117,22 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { } isPeriodStrategy(): boolean { - return this.getStrategyFormControl().value === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD; + return this.deleteTimeseriesFormGroup.get('strategy').value === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD; } isDeleteLatestStrategy(): boolean { - return this.getStrategyFormControl().value === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE; - } - - getStrategyFormControl(): AbstractControl { - return this.deleteTimeseriesFormGroup.get('strategy'); - } - - getStartDateTimeFormControl(): AbstractControl { - return this.deleteTimeseriesFormGroup.get('startDateTime'); - } - - getEndDateTimeFormControl(): AbstractControl { - return this.deleteTimeseriesFormGroup.get('endDateTime'); + return this.deleteTimeseriesFormGroup.get('strategy').value === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE; } onStartDateTimeChange(newStartDateTime: Date) { if (newStartDateTime) { const endDateTimeTs = this.deleteTimeseriesFormGroup.get('endDateTime').value.getTime(); - const startDateTimeControl = this.getStartDateTimeFormControl(); if (newStartDateTime.getTime() >= endDateTimeTs) { - startDateTimeControl.patchValue(new Date(endDateTimeTs - MINUTE), {onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('startDateTime') + .patchValue(new Date(endDateTimeTs - MINUTE), {onlySelf: true, emitEvent: false}); } else { - startDateTimeControl.patchValue(newStartDateTime, {onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('startDateTime') + .patchValue(newStartDateTime, {onlySelf: true, emitEvent: false}); } } } @@ -137,11 +140,12 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { onEndDateTimeChange(newEndDateTime: Date) { if (newEndDateTime) { const startDateTimeTs = this.deleteTimeseriesFormGroup.get('startDateTime').value.getTime(); - const endDateTimeControl = this.getEndDateTimeFormControl(); if (newEndDateTime.getTime() <= startDateTimeTs) { - endDateTimeControl.patchValue(new Date(startDateTimeTs + MINUTE), {onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('endDateTime') + .patchValue(new Date(startDateTimeTs + MINUTE), {onlySelf: true, emitEvent: false}); } else { - endDateTimeControl.patchValue(newEndDateTime, {onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('endDateTime') + .patchValue(newEndDateTime, {onlySelf: true, emitEvent: false}); } } } From 47543ad5e252548dba80c51e826088e747f29801 Mon Sep 17 00:00:00 2001 From: rusikv Date: Thu, 3 Aug 2023 18:33:23 +0300 Subject: [PATCH 26/51] Added selection to alarms table for bulk acknowledge and clear --- .../components/alarm/alarm-table-config.ts | 89 ++++++++++++++++++- .../assets/locale/locale.constant-en_US.json | 2 + 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index 28e64738a8..646e05511f 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -26,17 +26,17 @@ import { DatePipe } from '@angular/common'; import { Direction } from '@shared/models/page/sort-order'; import { MatDialog } from '@angular/material/dialog'; import { TimePageLink } from '@shared/models/page/page-link'; -import { Observable } from 'rxjs'; +import { forkJoin, Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; import { EntityId } from '@shared/models/id/entity-id'; import { AlarmInfo, - AlarmQuery, AlarmQueryV2, AlarmSearchStatus, alarmSeverityColors, alarmSeverityTranslations, AlarmsMode, + AlarmStatus, alarmStatusTranslations } from '@app/shared/models/alarm.models'; import { AlarmService } from '@app/core/http/alarm.service'; @@ -46,7 +46,7 @@ import { AlarmDetailsDialogComponent, AlarmDetailsDialogData } from '@home/components/alarm/alarm-details-dialog.component'; -import { DAY, forAllTimeInterval, historyInterval } from '@shared/models/time/time.models'; +import { forAllTimeInterval } from '@shared/models/time/time.models'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; @@ -93,7 +93,7 @@ export class AlarmTableConfig extends EntityTableConfig this.pageMode = pageMode; this.defaultTimewindowInterval = forAllTimeInterval(); this.detailsPanelEnabled = false; - this.selectionEnabled = false; + this.selectionEnabled = true; this.searchEnabled = true; this.addEnabled = false; this.entitiesDeleteEnabled = false; @@ -155,6 +155,23 @@ export class AlarmTableConfig extends EntityTableConfig onAction: ($event, entity) => this.showAlarmDetails(entity) } ); + + this.groupActionDescriptors.push( + { + name: this.translate.instant('alarm.acknowledge'), + icon: 'done', + isEnabled: true, + onAction: ($event, entities) => this.ackAlarms($event, entities) + } + ) + this.groupActionDescriptors.push( + { + name: this.translate.instant('alarm.clear'), + icon: 'clear', + isEnabled: true, + onAction: ($event, entities) => this.clearAlarms($event, entities) + } + ) } fetchAlarms(pageLink: TimePageLink): Observable> { @@ -294,4 +311,68 @@ export class AlarmTableConfig extends EntityTableConfig }); } + ackAlarms($event: Event, alarms: Array) { + if ($event) { + $event.stopPropagation(); + } + const unacknowledgedAlarms = alarms.filter(alarm => { + return alarm.status === AlarmStatus.CLEARED_UNACK || alarm.status === AlarmStatus.ACTIVE_UNACK; + }) + if (!unacknowledgedAlarms.length) { + this.dialogService.alert(this.translate.instant('alarm.selected-alarms', {count: alarms.length}), + this.translate.instant('alarm.selected-alarms-are-acknowledged')).subscribe(); + } else { + const title = this.translate.instant('alarm.aknowledge-alarms-title', {count: unacknowledgedAlarms.length}); + const content = this.translate.instant('alarm.aknowledge-alarms-text', {count: unacknowledgedAlarms.length}); + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes') + ).subscribe((res) => { + if (res) { + const tasks: Observable[] = []; + for (const alarm of unacknowledgedAlarms) { + tasks.push(this.alarmService.ackAlarm(alarm.id.id)); + } + forkJoin(tasks).subscribe(() => { + this.updateData(); + }); + } + }); + } + } + + clearAlarms($event: Event, alarms: Array) { + if ($event) { + $event.stopPropagation(); + } + const activeAlarms = alarms.filter(alarm => { + return alarm.status === AlarmStatus.ACTIVE_ACK || alarm.status === AlarmStatus.ACTIVE_UNACK; + }) + if (!activeAlarms.length) { + this.dialogService.alert(this.translate.instant('alarm.selected-alarms', {count: alarms.length}), + this.translate.instant('alarm.selected-alarms-are-cleared')).subscribe(); + } else { + const title = this.translate.instant('alarm.clear-alarms-title', {count: activeAlarms.length}); + const content = this.translate.instant('alarm.clear-alarms-text', {count: activeAlarms.length}); + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes') + ).subscribe((res) => { + if (res) { + const tasks: Observable[] = []; + for (const alarm of activeAlarms) { + tasks.push(this.alarmService.clearAlarm(alarm.id.id)); + } + forkJoin(tasks).subscribe(() => { + this.updateData(); + }); + } + }); + } + } + } 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 ba6bbda7ad..f387b2fe40 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -521,10 +521,12 @@ "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, =1 {1 alarm} other {# alarms} }?", "aknowledge-alarm-title": "Acknowledge Alarm", "aknowledge-alarm-text": "Are you sure you want to acknowledge Alarm?", + "selected-alarms-are-acknowledged": "Selected alarms are already acknowledged", "clear-alarms-title": "Clear { count, plural, =1 {1 alarm} other {# alarms} }", "clear-alarms-text": "Are you sure you want to clear { count, plural, =1 {1 alarm} other {# alarms} }?", "clear-alarm-title": "Clear Alarm", "clear-alarm-text": "Are you sure you want to clear Alarm?", + "selected-alarms-are-cleared": "Selected alarms are already cleared", "alarm-status-filter": "Alarm Status Filter", "alarm-filter-title": "Alarm Filter", "assigned": "Assigned", From a85f9fbabcf68da5319598174825c0d4d38fee97 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 4 Aug 2023 17:49:52 +0300 Subject: [PATCH 27/51] UI: Refactoring delete telemetry --- ui-ngx/src/app/core/http/attribute.service.ts | 12 +++- .../attribute/attribute-table.component.html | 2 +- .../attribute/attribute-table.component.ts | 65 +++++++++-------- .../delete-timeseries-panel.component.html | 72 +++++++++---------- .../delete-timeseries-panel.component.scss | 28 ++++++-- .../delete-timeseries-panel.component.ts | 25 ++++--- 6 files changed, 114 insertions(+), 90 deletions(-) diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index c810a0af22..9022a47eef 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -53,8 +53,16 @@ export class AttributeService { startTs?: number, endTs?: number, rewriteLatestIfDeleted = false, deleteLatest = true, config?: RequestConfig): Observable { const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(','); - let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` + - `?keys=${keys}&deleteAllDataForKeys=${deleteAllDataForKeys}&rewriteLatestIfDeleted=${rewriteLatestIfDeleted}&deleteLatest=${deleteLatest}`; + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete?keys=${keys}`; + if (isDefinedAndNotNull(deleteAllDataForKeys)) { + url += `&deleteAllDataForKeys=${deleteAllDataForKeys}`; + } + if (isDefinedAndNotNull(rewriteLatestIfDeleted)) { + url += `&rewriteLatestIfDeleted=${rewriteLatestIfDeleted}`; + } + if (isDefinedAndNotNull(deleteLatest)) { + url += `&deleteLatest=${deleteLatest}`; + } if (isDefinedAndNotNull(startTs)) { url += `&startTs=${startTs}`; } diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index 10a5b03dba..d94f08e29f 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -198,7 +198,7 @@ edit - diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts index add0a5c1e0..0da11767a2 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts @@ -38,7 +38,7 @@ import { TranslateService } from '@ngx-translate/core'; import { MatDialog } from '@angular/material/dialog'; import { DialogService } from '@core/services/dialog.service'; import { Direction, SortOrder } from '@shared/models/page/sort-order'; -import { fromEvent, merge, Observable } from 'rxjs'; +import { fromEvent, merge } from 'rxjs'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; import { EntityId } from '@shared/models/id/entity-id'; import { @@ -48,7 +48,8 @@ import { isClientSideTelemetryType, LatestTelemetry, TelemetryType, - telemetryTypeTranslations, TimeseriesDeleteStrategy, + telemetryTypeTranslations, + TimeseriesDeleteStrategy, toTelemetryType } from '@shared/models/telemetry/telemetry.models'; import { AttributeDatasource } from '@home/models/datasource/attribute-datasource'; @@ -88,7 +89,8 @@ import { hidePageSizePixelValue } from '@shared/models/constants'; import { ResizeObserver } from '@juggle/resize-observer'; import { DELETE_TIMESERIES_PANEL_DATA, - DeleteTimeseriesPanelComponent, DeleteTimeseriesPanelData + DeleteTimeseriesPanelComponent, + DeleteTimeseriesPanelData } from '@home/components/attribute/delete-timeseries-panel.component'; @@ -383,15 +385,19 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI }); } - deleteTimeseries($event: Event, attribute?: AttributeData) { + deleteTimeseries($event: Event, telemetry?: AttributeData) { if ($event) { $event.stopPropagation(); } - const isMultipleDeletion = isUndefinedOrNull(attribute) && this.dataSource.selection.selected.length > 1; + const isMultipleDeletion = isUndefinedOrNull(telemetry) && this.dataSource.selection.selected.length > 1; const target = $event.target || $event.srcElement || $event.currentTarget; - const config = new OverlayConfig(); - config.backdropClass = 'cdk-overlay-transparent-backdrop'; - config.hasBackdrop = true; + const config = new OverlayConfig({ + panelClass: 'tb-filter-panel', + backdropClass: 'cdk-overlay-transparent-backdrop', + hasBackdrop: true, + maxWidth: 488, + width: '100%' + }); const connectedPosition: ConnectedPosition = { originX: 'start', originY: 'top', @@ -400,8 +406,6 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI }; config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) .withPositions([connectedPosition]); - config.maxWidth = '488px'; - config.width = '100%'; const overlayRef = this.overlay.create(config); overlayRef.backdropClick().subscribe(() => { overlayRef.dispose(); @@ -411,7 +415,7 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI { provide: DELETE_TIMESERIES_PANEL_DATA, useValue: { - isMultipleDeletion: isMultipleDeletion + isMultipleDeletion } as DeleteTimeseriesPanelData }, { @@ -425,31 +429,34 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI componentRef.onDestroy(() => { if (componentRef.instance.result !== null) { const result = componentRef.instance.result; - const deleteTimeseries = attribute ? [attribute]: this.dataSource.selection.selected; + const deleteTimeseries = telemetry ? [telemetry]: this.dataSource.selection.selected; let deleteAllDataForKeys = false; let rewriteLatestIfDeleted = false; let startTs = null; let endTs = null; let deleteLatest = true; - if (result.strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA) { - deleteAllDataForKeys = true; - } - if (result.strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_EXCEPT_LATEST_VALUE) { - deleteAllDataForKeys = true; - deleteLatest = false; - } - if (result.strategy === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE) { - rewriteLatestIfDeleted = result.rewriteLatest; - startTs = deleteTimeseries[0].lastUpdateTs; - endTs = startTs + 1; - } - if (result.strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD) { - startTs = result.startDateTime.getTime(); - endTs = result.endDateTime.getTime(); - rewriteLatestIfDeleted = result.rewriteLatest; + switch (result.strategy) { + case TimeseriesDeleteStrategy.DELETE_ALL_DATA: + deleteAllDataForKeys = true; + break; + case TimeseriesDeleteStrategy.DELETE_ALL_DATA_EXCEPT_LATEST_VALUE: + deleteAllDataForKeys = true; + deleteLatest = false; + break; + case TimeseriesDeleteStrategy.DELETE_LATEST_VALUE: + rewriteLatestIfDeleted = result.rewriteLatest; + startTs = deleteTimeseries[0].lastUpdateTs; + endTs = startTs + 1; + break; + case TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD: + startTs = result.startDateTime.getTime(); + endTs = result.endDateTime.getTime(); + rewriteLatestIfDeleted = result.rewriteLatest; + break; } this.attributeService.deleteEntityTimeseries(this.entityIdValue, deleteTimeseries, deleteAllDataForKeys, - startTs, endTs, rewriteLatestIfDeleted, deleteLatest).subscribe(() => this.reloadAttributes()); + startTs, endTs, rewriteLatestIfDeleted, deleteLatest) + .subscribe(() => this.reloadAttributes()); } }); } diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html index aabac0bab4..5778fc6805 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html @@ -16,47 +16,41 @@ --> -
- -

{{ "attribute.delete-timeseries.delete-strategy" | translate }}

- - -
-
- - attribute.delete-timeseries.strategy - - - {{ strategiesTranslationsMap.get(strategy) | translate }} - - + +

{{ "attribute.delete-timeseries.delete-strategy" | translate }}

+ + +
+ + + attribute.delete-timeseries.strategy + + + {{ strategiesTranslationsMap.get(strategy) | translate }} + + + +
+ + attribute.delete-timeseries.start-time + + + + + + attribute.delete-timeseries.ends-on + + + -
-
- - attribute.delete-timeseries.start-time - - - - - - attribute.delete-timeseries.ends-on - - - - -
-
-
- - {{ "attribute.delete-timeseries.rewrite-latest-value" | translate }} - -
+ + {{ "attribute.delete-timeseries.rewrite-latest-value" | translate }} +
diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss index d223b29b47..c9a0e527f7 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss @@ -13,16 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@import '../../../../../scss/constants'; :host { width: 100%; - background-color: #fff; - box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3), 0px 2px 6px 2px rgba(0, 0, 0, 0.15); - border-radius: 4px; -} -:host ::ng-deep{ - form .mat-toolbar { + .mat-toolbar { background: none; } + + .tb-form-settings { + flex-direction: column; + gap: 16px; + padding-top: 0; + } + + .tb-select-interval { + display: flex; + flex-direction: row; + gap: 16px; + @media #{$mat-xs} { + flex-direction: column; + gap: 0; + } + } + + .tb-slide-toggle { + margin-bottom: 8px; + } } diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts index 8adb9bcd5a..94c66b3734 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts @@ -51,34 +51,33 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { strategiesTranslationsMap = timeseriesDeleteStrategyTranslations; - multipleDeletionStrategies = [ + private multipleDeletionStrategies = new Set([ TimeseriesDeleteStrategy.DELETE_ALL_DATA, TimeseriesDeleteStrategy.DELETE_ALL_DATA_EXCEPT_LATEST_VALUE - ]; + ]); private destroy$ = new Subject(); - constructor(@Inject(DELETE_TIMESERIES_PANEL_DATA) public data: DeleteTimeseriesPanelData, - public overlayRef: OverlayRef, - public fb: FormBuilder) { } + constructor(@Inject(DELETE_TIMESERIES_PANEL_DATA) private data: DeleteTimeseriesPanelData, + private overlayRef: OverlayRef, + private fb: FormBuilder) { } ngOnInit(): void { const today = new Date(); if (this.data.isMultipleDeletion) { - this.strategiesTranslationsMap = new Map(Array.from(this.strategiesTranslationsMap.entries()) - .filter(([strategy]) => { - return this.multipleDeletionStrategies.includes(strategy); - })) + this.strategiesTranslationsMap = new Map(Array.from(this.strategiesTranslationsMap) + .filter(([strategy]) => this.multipleDeletionStrategies.has(strategy))) } this.deleteTimeseriesFormGroup = this.fb.group({ strategy: [TimeseriesDeleteStrategy.DELETE_ALL_DATA], startDateTime: [ { value: new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()), disabled: true }, - [Validators.required] + Validators.required ], - endDateTime: [{ value: today, disabled: true }, [Validators.required]], + endDateTime: [{ value: today, disabled: true }, Validators.required], rewriteLatest: [true] }) + this.deleteTimeseriesFormGroup.get('strategy').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe(value => { @@ -124,7 +123,7 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { return this.deleteTimeseriesFormGroup.get('strategy').value === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE; } - onStartDateTimeChange(newStartDateTime: Date) { + private onStartDateTimeChange(newStartDateTime: Date) { if (newStartDateTime) { const endDateTimeTs = this.deleteTimeseriesFormGroup.get('endDateTime').value.getTime(); if (newStartDateTime.getTime() >= endDateTimeTs) { @@ -137,7 +136,7 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { } } - onEndDateTimeChange(newEndDateTime: Date) { + private onEndDateTimeChange(newEndDateTime: Date) { if (newEndDateTime) { const startDateTimeTs = this.deleteTimeseriesFormGroup.get('startDateTime').value.getTime(); if (newEndDateTime.getTime() <= startDateTimeTs) { From dd19109034ddaa14380e1b0d156eb6e0318e25fe Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 7 Aug 2023 16:11:14 +0300 Subject: [PATCH 28/51] Add delete button to selection in alarm table --- ui-ngx/src/app/core/http/alarm.service.ts | 12 ++-- .../components/alarm/alarm-table-config.ts | 65 +++++++++++++++---- .../lib/alarms-table-widget.component.ts | 4 +- .../assets/locale/locale.constant-en_US.json | 3 + 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/ui-ngx/src/app/core/http/alarm.service.ts b/ui-ngx/src/app/core/http/alarm.service.ts index e03e9ab783..f0ce187341 100644 --- a/ui-ngx/src/app/core/http/alarm.service.ts +++ b/ui-ngx/src/app/core/http/alarm.service.ts @@ -52,12 +52,12 @@ export class AlarmService { return this.http.post('/api/alarm', alarm, defaultHttpOptionsFromConfig(config)); } - public ackAlarm(alarmId: string, config?: RequestConfig): Observable { - return this.http.post(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptionsFromConfig(config)); + public ackAlarm(alarmId: string, config?: RequestConfig): Observable { + return this.http.post(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptionsFromConfig(config)); } - public clearAlarm(alarmId: string, config?: RequestConfig): Observable { - return this.http.post(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config)); + public clearAlarm(alarmId: string, config?: RequestConfig): Observable { + return this.http.post(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config)); } public assignAlarm(alarmId: string, assigneeId: string, config?: RequestConfig): Observable { @@ -68,8 +68,8 @@ export class AlarmService { return this.http.delete(`/api/alarm/${alarmId}/assign`, defaultHttpOptionsFromConfig(config)); } - public deleteAlarm(alarmId: string, config?: RequestConfig): Observable { - return this.http.delete(`/api/alarm/${alarmId}`, defaultHttpOptionsFromConfig(config)); + public deleteAlarm(alarmId: string, config?: RequestConfig): Observable { + return this.http.delete(`/api/alarm/${alarmId}`, defaultHttpOptionsFromConfig(config)); } public getAlarms(query: AlarmQuery, diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index 646e05511f..15e3fb1952 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -162,14 +162,18 @@ export class AlarmTableConfig extends EntityTableConfig icon: 'done', isEnabled: true, onAction: ($event, entities) => this.ackAlarms($event, entities) - } - ) - this.groupActionDescriptors.push( + }, { name: this.translate.instant('alarm.clear'), icon: 'clear', isEnabled: true, onAction: ($event, entities) => this.clearAlarms($event, entities) + }, + { + name: this.translate.instant('alarm.delete'), + icon: 'delete', + isEnabled: true, + onAction: ($event, entities) => this.deleteAlarms($event, entities) } ) } @@ -318,12 +322,17 @@ export class AlarmTableConfig extends EntityTableConfig const unacknowledgedAlarms = alarms.filter(alarm => { return alarm.status === AlarmStatus.CLEARED_UNACK || alarm.status === AlarmStatus.ACTIVE_UNACK; }) + let title = ''; + let content = ''; if (!unacknowledgedAlarms.length) { - this.dialogService.alert(this.translate.instant('alarm.selected-alarms', {count: alarms.length}), - this.translate.instant('alarm.selected-alarms-are-acknowledged')).subscribe(); + title = this.translate.instant('alarm.selected-alarms', {count: alarms.length}); + content = this.translate.instant('alarm.selected-alarms-are-acknowledged'); + this.dialogService.alert( + title, + content).subscribe(); } else { - const title = this.translate.instant('alarm.aknowledge-alarms-title', {count: unacknowledgedAlarms.length}); - const content = this.translate.instant('alarm.aknowledge-alarms-text', {count: unacknowledgedAlarms.length}); + title = this.translate.instant('alarm.aknowledge-alarms-title', {count: unacknowledgedAlarms.length}); + content = this.translate.instant('alarm.aknowledge-alarms-text', {count: unacknowledgedAlarms.length}); this.dialogService.confirm( title, content, @@ -331,7 +340,7 @@ export class AlarmTableConfig extends EntityTableConfig this.translate.instant('action.yes') ).subscribe((res) => { if (res) { - const tasks: Observable[] = []; + const tasks: Observable[] = []; for (const alarm of unacknowledgedAlarms) { tasks.push(this.alarmService.ackAlarm(alarm.id.id)); } @@ -350,12 +359,18 @@ export class AlarmTableConfig extends EntityTableConfig const activeAlarms = alarms.filter(alarm => { return alarm.status === AlarmStatus.ACTIVE_ACK || alarm.status === AlarmStatus.ACTIVE_UNACK; }) + let title = ''; + let content = ''; if (!activeAlarms.length) { - this.dialogService.alert(this.translate.instant('alarm.selected-alarms', {count: alarms.length}), - this.translate.instant('alarm.selected-alarms-are-cleared')).subscribe(); + title = this.translate.instant('alarm.selected-alarms', {count: alarms.length}); + content = this.translate.instant('alarm.selected-alarms-are-cleared'); + this.dialogService.alert( + title, + content + ).subscribe(); } else { - const title = this.translate.instant('alarm.clear-alarms-title', {count: activeAlarms.length}); - const content = this.translate.instant('alarm.clear-alarms-text', {count: activeAlarms.length}); + title = this.translate.instant('alarm.clear-alarms-title', {count: activeAlarms.length}); + content = this.translate.instant('alarm.clear-alarms-text', {count: activeAlarms.length}); this.dialogService.confirm( title, content, @@ -363,7 +378,7 @@ export class AlarmTableConfig extends EntityTableConfig this.translate.instant('action.yes') ).subscribe((res) => { if (res) { - const tasks: Observable[] = []; + const tasks: Observable[] = []; for (const alarm of activeAlarms) { tasks.push(this.alarmService.clearAlarm(alarm.id.id)); } @@ -375,4 +390,28 @@ export class AlarmTableConfig extends EntityTableConfig } } + deleteAlarms($event: Event, alarms: Array) { + if ($event) { + $event.stopPropagation(); + } + const title = this.translate.instant('alarm.delete-alarms-title', {count: alarms.length}); + const content = this.translate.instant('alarm.delete-alarms-text', {count: alarms.length}); + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes') + ).subscribe((res) => { + if (res) { + const tasks: Observable[] = []; + for (const alarm of alarms) { + tasks.push(this.alarmService.deleteAlarm(alarm.id.id)); + } + forkJoin(tasks).subscribe(() => { + this.updateData(); + }); + } + }); + } + } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index f799ed43ff..57e8db694c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -927,7 +927,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, ).subscribe((res) => { if (res) { if (res) { - const tasks: Observable[] = []; + const tasks: Observable[] = []; for (const alarmId of alarmIds) { tasks.push(this.alarmService.ackAlarm(alarmId)); } @@ -983,7 +983,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, ).subscribe((res) => { if (res) { if (res) { - const tasks: Observable[] = []; + const tasks: Observable[] = []; for (const alarmId of alarmIds) { tasks.push(this.alarmService.clearAlarm(alarmId)); } 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 756cbb3ab6..ce134c1fd4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -512,6 +512,7 @@ "severity-indeterminate": "Indeterminate", "acknowledge": "Acknowledge", "clear": "Clear", + "delete": "Delete", "search": "Search alarms", "selected-alarms": "{ count, plural, =1 {1 alarm} other {# alarms} } selected", "no-data": "No data to display", @@ -527,6 +528,8 @@ "clear-alarms-text": "Are you sure you want to clear { count, plural, =1 {1 alarm} other {# alarms} }?", "clear-alarm-title": "Clear Alarm", "clear-alarm-text": "Are you sure you want to clear Alarm?", + "delete-alarms-title": "Delete { count, plural, =1 {1 alarm} other {# alarms} }", + "delete-alarms-text": "Are you sure you want to delete { count, plural, =1 {1 alarm} other {# alarms} }?", "selected-alarms-are-cleared": "Selected alarms are already cleared", "alarm-status-filter": "Alarm Status Filter", "alarm-filter-title": "Alarm Filter", From fe7846d1520ae4d06311ebcf6b54161a814f024b Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 19 Aug 2021 15:23:16 +0200 Subject: [PATCH 29/51] ui: event table: default interval is 15 minutes --- .../src/app/modules/home/components/event/event-table-config.ts | 2 ++ 1 file changed, 2 insertions(+) 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 a9816a130c..8b0ea62abb 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 @@ -40,6 +40,7 @@ import { EventContentDialogData } from '@home/components/event/event-content-dialog.component'; import { isEqual, sortObjectKeys } from '@core/utils'; +import {historyInterval, MINUTE} from '@shared/models/time/time.models'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { ChangeDetectorRef, Injector, StaticProvider, ViewContainerRef } from '@angular/core'; import { ComponentPortal } from '@angular/cdk/portal'; @@ -89,6 +90,7 @@ export class EventTableConfig extends EntityTableConfig { this.loadDataOnInit = false; this.tableTitle = ''; this.useTimePageLink = true; + this.defaultTimewindowInterval = historyInterval(MINUTE * 15); this.detailsPanelEnabled = false; this.selectionEnabled = false; this.searchEnabled = false; From 33bab60954e67220e278bf4399daf440d464efca Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 19 Aug 2021 15:22:46 +0200 Subject: [PATCH 30/51] ui: event table: ts with ms --- .../src/app/modules/home/components/event/event-table-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8b0ea62abb..a07967f9c3 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 @@ -178,7 +178,7 @@ export class EventTableConfig extends EntityTableConfig { updateColumns(updateTableColumns: boolean = false): void { this.columns = []; this.columns.push( - new DateEntityTableColumn('createdTime', 'event.event-time', this.datePipe, '120px'), + new DateEntityTableColumn('createdTime', 'event.event-time', this.datePipe, '120px', 'yyyy-MM-dd HH:mm:ss.SSS'), new EntityTableColumn('server', 'event.server', '100px', (entity) => entity.body.server, entity => ({}), false)); switch (this.eventType) { From b7d522295810b59201614f3df28fc59eabc18e0d Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Tue, 8 Aug 2023 17:16:02 +0300 Subject: [PATCH 31/51] UI: Updated code style --- .../src/app/modules/home/components/event/event-table-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a07967f9c3..eb3edb0baf 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 @@ -40,7 +40,7 @@ import { EventContentDialogData } from '@home/components/event/event-content-dialog.component'; import { isEqual, sortObjectKeys } from '@core/utils'; -import {historyInterval, MINUTE} from '@shared/models/time/time.models'; +import { historyInterval, MINUTE } from '@shared/models/time/time.models'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { ChangeDetectorRef, Injector, StaticProvider, ViewContainerRef } from '@angular/core'; import { ComponentPortal } from '@angular/cdk/portal'; From 32fae3a4b695871fda9013cf4b7f91772d664604 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 9 Aug 2023 11:33:22 +0300 Subject: [PATCH 32/51] tbel: add gecodeToJson(String.class) --- .../thingsboard/script/api/tbel/TbUtils.java | 5 +++++ .../script/api/tbel/TbUtilsTest.java | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index b337612011..bffd60f029 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java @@ -57,6 +57,8 @@ public class TbUtils { List.class))); parserConfig.addImport("decodeToJson", new MethodStub(TbUtils.class.getMethod("decodeToJson", ExecutionContext.class, List.class))); + parserConfig.addImport("decodeToJson", new MethodStub(TbUtils.class.getMethod("decodeToJson", + ExecutionContext.class, String.class))); parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes", ExecutionContext.class, String.class))); parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes", @@ -174,6 +176,9 @@ public class TbUtils { public static Object decodeToJson(ExecutionContext ctx, List bytesList) throws IOException { return TbJson.parse(ctx, bytesToString(bytesList)); } + public static Object decodeToJson(ExecutionContext ctx, String jsonStr) throws IOException { + return TbJson.parse(ctx, jsonStr); + } public static String bytesToString(List bytesList) { byte[] bytes = bytesFromList(bytesList); diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java index b6d5395af8..a16a70b962 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java @@ -27,6 +27,7 @@ import org.mvel2.SandboxedParserConfiguration; import org.mvel2.execution.ExecutionArrayList; import org.mvel2.execution.ExecutionHashMap; +import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -348,6 +349,24 @@ public class TbUtilsTest { Assert.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleVaList, 0, false))); } + @Test + public void parseBytesDecodeToJson() throws IOException { + String expectedStr = "{\"hello\": \"world\"}"; + ExecutionHashMap expectedJson = new ExecutionHashMap<>(1, ctx); + expectedJson.put("hello", "world"); + List expectedBytes = TbUtils.stringToBytes(ctx, expectedStr); + Object actualJson = TbUtils.decodeToJson(ctx, expectedBytes); + Assert.assertEquals(expectedJson,actualJson); + } + @Test + public void parseStringDecodeToJson() throws IOException { + String expectedStr = "{\"hello\": \"world\"}"; + ExecutionHashMap expectedJson = new ExecutionHashMap<>(1, ctx); + expectedJson.put("hello", "world"); + Object actualJson = TbUtils.decodeToJson(ctx, expectedStr); + Assert.assertEquals(expectedJson,actualJson); + } + private static List toList(byte[] data) { List result = new ArrayList<>(data.length); for (Byte b : data) { From ba85007a2dab10ba2bb8c0839b551e07c6a2f747 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 9 Aug 2023 10:59:13 +0200 Subject: [PATCH 33/51] minor improvements --- .../server/controller/TelemetryController.java | 10 ++++++---- ui-ngx/src/assets/locale/locale.constant-ca_ES.json | 1 + ui-ngx/src/assets/locale/locale.constant-cs_CZ.json | 1 + ui-ngx/src/assets/locale/locale.constant-da_DK.json | 1 + ui-ngx/src/assets/locale/locale.constant-de_DE.json | 1 + ui-ngx/src/assets/locale/locale.constant-el_GR.json | 1 + ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + ui-ngx/src/assets/locale/locale.constant-es_ES.json | 1 + ui-ngx/src/assets/locale/locale.constant-fa_IR.json | 1 + ui-ngx/src/assets/locale/locale.constant-fr_FR.json | 1 + ui-ngx/src/assets/locale/locale.constant-it_IT.json | 1 + ui-ngx/src/assets/locale/locale.constant-ja_JP.json | 1 + ui-ngx/src/assets/locale/locale.constant-ka_GE.json | 1 + ui-ngx/src/assets/locale/locale.constant-ko_KR.json | 1 + ui-ngx/src/assets/locale/locale.constant-lv_LV.json | 1 + ui-ngx/src/assets/locale/locale.constant-pt_BR.json | 1 + ui-ngx/src/assets/locale/locale.constant-ro_RO.json | 1 + ui-ngx/src/assets/locale/locale.constant-sl_SI.json | 1 + ui-ngx/src/assets/locale/locale.constant-tr_TR.json | 1 + ui-ngx/src/assets/locale/locale.constant-uk_UA.json | 1 + ui-ngx/src/assets/locale/locale.constant-zh_CN.json | 1 + ui-ngx/src/assets/locale/locale.constant-zh_TW.json | 1 + 22 files changed, 27 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 6937fa6c84..2445ee9bb6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -462,7 +462,9 @@ public class TelemetryController extends BaseController { notes = "Delete time-series for selected entity based on entity id, entity type and keys." + " Use 'deleteAllDataForKeys' to delete all time-series data." + " Use 'startTs' and 'endTs' to specify time-range instead. " + - " Use 'rewriteLatestIfDeleted' to rewrite latest value (stored in separate table for performance) after deletion of the time range. " + + " Use 'deleteLatest' to delete latest value (stored in separate table for performance) if the value's timestamp matches the time-range. " + + " Use 'rewriteLatestIfDeleted' to rewrite latest value (stored in separate table for performance) if the value's timestamp matches the time-range and 'deleteLatest' param is true." + + " The replacement value will be fetched from the 'time-series' table, and its timestamp will be the most recent one before the defined time-range. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) @ApiResponses(value = { @@ -486,10 +488,10 @@ public class TelemetryController extends BaseController { @RequestParam(name = "startTs", required = false) Long startTs, @ApiParam(value = "A long value representing the end timestamp of removal time range in milliseconds.") @RequestParam(name = "endTs", required = false) Long endTs, - @ApiParam(value = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.") - @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted, @ApiParam(value = "If the parameter is set to true, the latest telemetry can be removed, otherwise, in case that parameter is set to false the latest value will not removed.") - @RequestParam(name = "deleteLatest", required = false, defaultValue = "true") boolean deleteLatest) throws ThingsboardException { + @RequestParam(name = "deleteLatest", required = false, defaultValue = "true") boolean deleteLatest, + @ApiParam(value = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.") + @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted, deleteLatest); } diff --git a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json index 103d55a79c..34735dc1c5 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json @@ -633,6 +633,7 @@ "latest-telemetry": "Última telemetria", "attributes-scope": "Abast dels atributs del dispositiu", "scope-telemetry": "Telemetria", + "scope-latest-telemetry": "Última telemetria", "scope-client": "Atributs del Client", "scope-server": "Atributs del Servidor", "scope-shared": "Atributs Compartits", diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 2a79340240..25325acab7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -446,6 +446,7 @@ "latest-telemetry": "Poslední telemetrie", "attributes-scope": "Rozsah atributů entity", "scope-telemetry": "Telemetrie", + "scope-latest-telemetry": "Poslední telemetrie", "scope-client": "Atributy klienta", "scope-server": "Atributy serveru", "scope-shared": "Sdílené atributy", diff --git a/ui-ngx/src/assets/locale/locale.constant-da_DK.json b/ui-ngx/src/assets/locale/locale.constant-da_DK.json index 7389ef448d..1c26dd45a7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-da_DK.json +++ b/ui-ngx/src/assets/locale/locale.constant-da_DK.json @@ -454,6 +454,7 @@ "latest-telemetry": "Seneste telemetri", "attributes-scope": "Omfang af entitetsattributter", "scope-telemetry": "Telemetri", + "scope-latest-telemetry": "Seneste telemetri", "scope-client": "Klientattributter", "scope-server": "Serverattributter", "scope-shared": "Delte attributter", diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json index c27d63f4fb..d7b50d51c3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-de_DE.json +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -325,6 +325,7 @@ "latest-telemetry": "Neueste Telemetrie", "attributes-scope": "Entitätseigenschaftsbereich", "scope-telemetry": "Telemetrie", + "scope-latest-telemetry": "Neueste Telemetrie", "scope-client": "Client Eigenschaften", "scope-server": "Server Eigenschaften", "scope-shared": "Gemeinsame Eigenschaften", diff --git a/ui-ngx/src/assets/locale/locale.constant-el_GR.json b/ui-ngx/src/assets/locale/locale.constant-el_GR.json index 36e8e4e000..9c5ac0db54 100644 --- a/ui-ngx/src/assets/locale/locale.constant-el_GR.json +++ b/ui-ngx/src/assets/locale/locale.constant-el_GR.json @@ -292,6 +292,7 @@ "latest-telemetry": "Τελευταία τηλεμετρία", "attributes-scope": "Πεδίο εφαρμογής Χαρακτηριστικών Οντότητας", "scope-telemetry": "Τηλεμετρία", + "scope-latest-telemetry": "Τελευταία τηλεμετρία", "scope-client": "Χαρακτηριστικά Client", "scope-server": "Χαρακτηριστικά Server", "scope-shared": "Κοινόχρηστα Χαρακτηριστικά", 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 30c05e2edb..76484651b0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -700,6 +700,7 @@ "no-latest-telemetry": "No latest telemetry", "attributes-scope": "Entity attributes scope", "scope-telemetry": "Telemetry", + "scope-latest-telemetry": "Latest telemetry", "scope-client": "Client attributes", "scope-server": "Server attributes", "scope-shared": "Shared attributes", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index ec4f93823b..cbbaf4b5a1 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -668,6 +668,7 @@ "latest-telemetry": "Última telemetría", "attributes-scope": "Alcance de los atributos del dispositivo", "scope-telemetry": "Telemetría", + "scope-latest-telemetry": "Última telemetría", "scope-client": "Atributos de Cliente", "scope-server": "Atributos de Servidor", "scope-shared": "Atributos Compartidos", diff --git a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json index da841a6e53..6ab40820ec 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json @@ -255,6 +255,7 @@ "latest-telemetry": "آخرين سنجش", "attributes-scope": "حوزه ويژگي هاي موجودي", "scope-telemetry": "تله متری", + "scope-latest-telemetry": "آخرين سنجش", "scope-client": "ويژگي هاي مشتري", "scope-server": "ويژگي هاي سِروِر", "scope-shared": "ويژگي هاي مشترک", diff --git a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json index c021784db7..3db8795df6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -460,6 +460,7 @@ "prev-widget": "Widget précédent", "scope-client": "Attributs du client", "scope-telemetry": "Télémétrie", + "scope-latest-telemetry": "Dernière télémétrie", "scope-server": "Attributs du serveur", "scope-shared": "Attributs partagés", "selected-attributes": "{count, plural, =1 {1 attribut} other {# attributs} } sélectionnés", diff --git a/ui-ngx/src/assets/locale/locale.constant-it_IT.json b/ui-ngx/src/assets/locale/locale.constant-it_IT.json index 2c093e76a9..61f6554049 100644 --- a/ui-ngx/src/assets/locale/locale.constant-it_IT.json +++ b/ui-ngx/src/assets/locale/locale.constant-it_IT.json @@ -277,6 +277,7 @@ "latest-telemetry": "Ultima telemetria", "attributes-scope": "Visibilità attributi entità", "scope-telemetry": "Telemetria", + "scope-latest-telemetry": "Ultima telemetria", "scope-client": "Attributi client", "scope-server": "Attributi server", "scope-shared": "Attributi condivisi", diff --git a/ui-ngx/src/assets/locale/locale.constant-ja_JP.json b/ui-ngx/src/assets/locale/locale.constant-ja_JP.json index 23145c1f89..56130aabc3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ja_JP.json +++ b/ui-ngx/src/assets/locale/locale.constant-ja_JP.json @@ -245,6 +245,7 @@ "latest-telemetry": "最新テレメトリ", "attributes-scope": "エンティティ属性のスコープ", "scope-telemetry": "テレメトリー", + "scope-latest-telemetry": "最新テレメトリ", "scope-client": "クライアントの属性", "scope-server": "サーバーの属性", "scope-shared": "共有属性", diff --git a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json index 89d25703e3..55b6e85f66 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json +++ b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json @@ -291,6 +291,7 @@ "latest-telemetry": "უახლესი ტელემეტრია", "attributes-scope": "ობიექტის ატრიბუტების ფარგლები", "scope-telemetry": "ტელემეტრია", + "scope-latest-telemetry": "უახლესი ტელემეტრია", "scope-client": "კლიენტის ატრიბუტები", "scope-server": "სერვერის ატრიბუტები", "scope-shared": "ატრიბუტების გაზიარება", diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json index c1be51d1db..ae53707a52 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -409,6 +409,7 @@ "latest-telemetry": "최근 데이터", "attributes-scope": "장치 속성 범위", "scope-telemetry": "원격 측정", + "scope-latest-telemetry": "최근 데이터", "scope-client": "클라이언트 속성", "scope-server": "서버 속성", "scope-shared": "공유 속성", diff --git a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json index f4f5befc14..00e6be4714 100644 --- a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json +++ b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json @@ -257,6 +257,7 @@ "latest-telemetry": "Jaunākā telemetrija", "attributes-scope": "Vienības atribūtu darbības joma", "scope-telemetry": "Telemetrija", + "scope-latest-telemetry": "Jaunākā telemetrija", "scope-client": "Klientu atribūti", "scope-server": "Servera atribūti", "scope-shared": "Dalītie atribūti", diff --git a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json index 28c7082df9..12ad7743c4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json +++ b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json @@ -310,6 +310,7 @@ "latest-telemetry": "Última telemetria", "attributes-scope": "Escopo de atributos de entidade", "scope-telemetry": "Telemetria", + "scope-latest-telemetry": "Última telemetria", "scope-client": "Atributos do cliente", "scope-server": "Atributos do servidor", "scope-shared": "Atributos compartilhados", diff --git a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json index da0012e6f6..4b565d8311 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json +++ b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json @@ -286,6 +286,7 @@ "latest-telemetry": "Ultimele Date Telemetrice", "attributes-scope": "Scop Atribute Entitate", "scope-telemetry": "Telemetrie", + "scope-latest-telemetry": "Ultimele Date Telemetrice", "scope-client": "Atribute Client", "scope-server": "Atribute Server", "scope-shared": "Atribute Partajate", diff --git a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json index 7875163b60..0515b5890e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json +++ b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json @@ -409,6 +409,7 @@ "latest-telemetry": "Najnovejša telemetrija", "attributes-scope": "Obseg atributov entitete", "scope-telemetry": "Telemetrija", + "scope-latest-telemetry": "Najnovejša telemetrija", "scope-client": "Atributi odjemalca", "scope-server": "Atributi strežnika", "scope-shared": "Skupni atributi", diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json index c56be48c74..8590dd2768 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -446,6 +446,7 @@ "latest-telemetry": "Son telemetri", "attributes-scope": "Varlık öznitelik kapsamı", "scope-telemetry": "telemetri", + "scope-latest-telemetry": "Son telemetri", "scope-client": "İstemci öznitelikler", "scope-server": "Sunucu öznitelikler", "scope-shared": "Paylaşılan öznitelikler", diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index bd608cd709..c7afaad234 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -343,6 +343,7 @@ "latest-telemetry": "Остання телеметрія", "attributes-scope": "Область видимості атрибутів", "scope-telemetry": "Телеметрія", + "scope-latest-telemetry": "Остання телеметрія", "scope-client": "Клієнтські атрибути", "scope-server": "Серверні атрибути", "scope-shared": "Спільні атрибути", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index 2fd73b32bd..6e5bf9a6b6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -591,6 +591,7 @@ "latest-telemetry": "最新遥测数据", "attributes-scope": "设备属性范围", "scope-telemetry": "遥测", + "scope-latest-telemetry": "最新遥测数据", "scope-client": "客户端属性", "scope-server": "服务端属性", "scope-shared": "共享属性", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index 526597ad70..2b98451f20 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -520,6 +520,7 @@ "latest-telemetry": "最新遙測", "attributes-scope": "設備屬性範圍", "scope-telemetry": "遙測", + "scope-latest-telemetry": "最新遙測", "scope-client": "客戶端屬性", "scope-server": "服務端屬性", "scope-shared": "共享屬性", From 141a7ff0e6be9bb132e788c870b6689f84fb8b5b Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 9 Aug 2023 21:21:15 +0200 Subject: [PATCH 34/51] changed recalculate_delay --- application/src/main/resources/thingsboard.yml | 5 ++++- .../server/queue/discovery/ZkDiscoveryService.java | 2 +- msa/vc-executor/src/main/resources/tb-vc-executor.yml | 2 +- transport/coap/src/main/resources/tb-coap-transport.yml | 2 +- transport/http/src/main/resources/tb-http-transport.yml | 2 +- transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml | 2 +- transport/mqtt/src/main/resources/tb-mqtt-transport.yml | 2 +- transport/snmp/src/main/resources/tb-snmp-transport.yml | 2 +- 8 files changed, 11 insertions(+), 8 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 1f16fbc414..5b76175fcd 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -96,7 +96,10 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + # The recalculate_delay property recommended in a microservices architecture setup for rule-engine services. + # This property provides a pause to ensure that when a rule-engine service is restarted, other nodes don't immediately attempt to recalculate their partitions. + # The delay is recommended because the initialization of rule chain actors is time-consuming. Avoiding unnecessary recalculations during a restart can enhance system performance and stability. + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cluster: stats: diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 44999d016a..e99817de17 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -69,7 +69,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private Integer zkSessionTimeout; @Value("${zk.zk_dir}") private String zkDir; - @Value("${zk.recalculate_delay:60000}") + @Value("${zk.recalculate_delay:0}") private Long recalculateDelay; protected final ConcurrentHashMap> delayedTasks; diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 66c6b4d3da..9e57a35e20 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" queue: type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index f4b5e0bc94..a545759f38 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index f92da86b99..1f042fb131 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -68,7 +68,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 05388473f0..ffe815d441 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index e131788929..f7d0209804 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index a7928eb49f..3ed46dde78 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cache: type: "${CACHE_TYPE:redis}" From 3d5cfa0c2ef8a8eee872288765f54bcd69e16c53 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 10 Aug 2023 15:46:01 +0300 Subject: [PATCH 35/51] added internal type to TbMsg to replace if-return blocks with switch-case --- .../server/common/data/msg/TbMsgType.java | 27 ++++++--- .../server/common/data/msg/TbMsgTypeTest.java | 24 ++++++-- .../thingsboard/server/common/msg/TbMsg.java | 59 ++++++++++++++++--- .../engine/filter/TbMsgTypeSwitchNode.java | 2 +- .../filter/TbMsgTypeSwitchNodeTest.java | 3 +- 5 files changed, 91 insertions(+), 24 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java index 206b203682..bd351ffecd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -76,7 +76,10 @@ public enum TbMsgType { DEVICE_UPDATE_SELF_MSG(null, true), DEDUPLICATION_TIMEOUT_SELF_MSG(null, true), DELAY_TIMEOUT_SELF_MSG(null, true), - MSG_COUNT_SELF_MSG(null, true); + MSG_COUNT_SELF_MSG(null, true), + + // Custom or N/A type: + CUSTOM_OR_NA_TYPE(null, false, true); public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() .filter(tbMsgType -> !tbMsgType.isTellSelfOnly()) @@ -90,26 +93,32 @@ public enum TbMsgType { @Getter private final boolean tellSelfOnly; + @Getter + private final boolean customType; + + TbMsgType(String ruleNodeConnection, boolean tellSelfOnly, boolean customType) { + this.ruleNodeConnection = ruleNodeConnection; + this.tellSelfOnly = tellSelfOnly; + this.customType = customType; + } + TbMsgType(String ruleNodeConnection, boolean tellSelfOnly) { this.ruleNodeConnection = ruleNodeConnection; this.tellSelfOnly = tellSelfOnly; + this.customType = false; } TbMsgType(String ruleNodeConnection) { this.ruleNodeConnection = ruleNodeConnection; this.tellSelfOnly = false; + this.customType = false; } - public static String getRuleNodeConnectionOrElseOther(String msgType) { - if (msgType == null) { + public static String getRuleNodeConnectionOrElseOther(TbMsgType msgType) { + if (msgType == null || msgType.isCustomType() || msgType.isTellSelfOnly()) { return TbNodeConnectionType.OTHER; - } else { - return Arrays.stream(TbMsgType.values()) - .filter(type -> type.name().equals(msgType)) - .findFirst() - .map(TbMsgType::getRuleNodeConnection) - .orElse(TbNodeConnectionType.OTHER); } + return Objects.requireNonNullElse(msgType.getRuleNodeConnection(), TbNodeConnectionType.OTHER); } } diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java index a37eb31d72..ff41505266 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java @@ -22,6 +22,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM; import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; +import static org.thingsboard.server.common.data.msg.TbMsgType.CUSTOM_OR_NA_TYPE; import static org.thingsboard.server.common.data.msg.TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.DELAY_TIMEOUT_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED_TO_EDGE; @@ -51,11 +52,12 @@ class TbMsgTypeTest { DEVICE_UPDATE_SELF_MSG, DEDUPLICATION_TIMEOUT_SELF_MSG, DELAY_TIMEOUT_SELF_MSG, - MSG_COUNT_SELF_MSG + MSG_COUNT_SELF_MSG, + CUSTOM_OR_NA_TYPE ); // backward-compatibility tests - + @Test void getRuleNodeConnectionsTest() { var tbMsgTypes = TbMsgType.values(); @@ -75,13 +77,25 @@ class TbMsgTypeTest { var tbMsgTypes = TbMsgType.values(); for (var type : tbMsgTypes) { if (typesWithNullRuleNodeConnection.contains(type)) { - assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(type.name())) + assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(type)) .isEqualTo(TbNodeConnectionType.OTHER); } else { - assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(type.name())).isNotNull() + assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(type)).isNotNull() .isNotEqualTo(TbNodeConnectionType.OTHER); } } } - + + @Test + void getCustomTypeTest() { + var tbMsgTypes = TbMsgType.values(); + for (var type : tbMsgTypes) { + if (type.equals(CUSTOM_OR_NA_TYPE)) { + assertThat(type.isCustomType()).isTrue(); + continue; + } + assertThat(type.isCustomType()).isFalse(); + } + } + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index a987a4a253..b4f6ccd584 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -52,6 +52,7 @@ public final class TbMsg implements Serializable { private final UUID id; private final long ts; private final String type; + private final TbMsgType internalType; private final EntityId originator; private final CustomerId customerId; private final TbMsgMetaData metaData; @@ -117,7 +118,7 @@ public final class TbMsg implements Serializable { } public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { - return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); } @@ -126,7 +127,7 @@ public final class TbMsg implements Serializable { } public static TbMsg newMsg(TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { - return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); } @@ -205,12 +206,12 @@ public final class TbMsg implements Serializable { } public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { - return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); } public static TbMsg newMsg(TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { - return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), dataType, data, null, null, null, TbMsgCallback.EMPTY); } @@ -255,17 +256,17 @@ public final class TbMsg implements Serializable { } public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { - return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, null, + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); } public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) { - return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, null, + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, callback); } public static TbMsg transformMsg(TbMsg tbMsg, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { - return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type.name(), originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type, originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.callback); } @@ -315,6 +316,36 @@ public final class TbMsg implements Serializable { tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, tbMsg.ctx.copy(), TbMsgCallback.EMPTY); } + private TbMsg(String queueName, UUID id, long ts, TbMsgType internalType, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data, + RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgProcessingCtx ctx, TbMsgCallback callback) { + this.id = id; + this.queueName = queueName; + if (ts > 0) { + this.ts = ts; + } else { + this.ts = System.currentTimeMillis(); + } + this.internalType = internalType; + this.type = internalType.name(); + this.originator = originator; + if (customerId == null || customerId.isNullUid()) { + if (originator != null && originator.getEntityType() == EntityType.CUSTOMER) { + this.customerId = (CustomerId) originator; + } else { + this.customerId = null; + } + } else { + this.customerId = customerId; + } + this.metaData = metaData; + this.dataType = dataType; + this.data = data; + this.ruleChainId = ruleChainId; + this.ruleNodeId = ruleNodeId; + this.ctx = ctx != null ? ctx : new TbMsgProcessingCtx(); + this.callback = Objects.requireNonNullElse(callback, TbMsgCallback.EMPTY); + } + private TbMsg(String queueName, UUID id, long ts, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgProcessingCtx ctx, TbMsgCallback callback) { this.id = id; @@ -325,6 +356,7 @@ public final class TbMsg implements Serializable { this.ts = System.currentTimeMillis(); } this.type = type; + this.internalType = getInternalType(); this.originator = originator; if (customerId == null || customerId.isNullUid()) { if (originator != null && originator.getEntityType() == EntityType.CUSTOMER) { @@ -468,8 +500,19 @@ public final class TbMsg implements Serializable { return ts; } + public TbMsgType getInternalType() { + if (internalType != null) { + return internalType; + } + try { + return TbMsgType.valueOf(type); + } catch (IllegalArgumentException e) { + return TbMsgType.CUSTOM_OR_NA_TYPE; + } + } + public boolean isTypeOf(TbMsgType tbMsgType) { - return tbMsgType != null && tbMsgType.name().equals(this.type); + return tbMsgType != null && tbMsgType.equals(getInternalType()); } public boolean isTypeOneOf(TbMsgType... types) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index 2121e0c5fa..068d342ea7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -50,7 +50,7 @@ public class TbMsgTypeSwitchNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ctx.tellNext(msg, TbMsgType.getRuleNodeConnectionOrElseOther(msg.getType())); + ctx.tellNext(msg, TbMsgType.getRuleNodeConnectionOrElseOther(msg.getInternalType())); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java index c4fc8cd76d..7861b2f489 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java @@ -81,9 +81,10 @@ class TbMsgTypeSwitchNodeTest { var msg = resultMsgs.get(i); assertThat(msg).isNotNull(); assertThat(msg.getType()).isNotNull(); + assertThat(msg.getType()).isEqualTo(msg.getInternalType().name()); assertThat(msg).isSameAs(tbMsgList.get(i)); assertThat(resultNodeConnections.get(i)) - .isEqualTo(TbMsgType.getRuleNodeConnectionOrElseOther(msg.getType())); + .isEqualTo(TbMsgType.getRuleNodeConnectionOrElseOther(msg.getInternalType())); } } From cba324f5bae50233c1110b6fd4696d34f98f53a7 Mon Sep 17 00:00:00 2001 From: kalytka Date: Thu, 10 Aug 2023 16:41:15 +0300 Subject: [PATCH 36/51] UI: Refactoring component for used filter and enrichment rule nodes --- .../relation/relation-filters.component.html | 70 +++++++++---------- .../relation/relation-filters.component.scss | 44 +++--------- .../basic/common/data-key-row.component.html | 2 +- .../common/data-keys-panel.component.html | 4 +- .../add-rule-node-dialog.component.scss | 4 ++ .../rule-node-details.component.scss | 5 +- .../entity/entity-subtype-list.component.html | 13 ++-- .../entity/entity-subtype-list.component.ts | 48 ++++++++----- .../entity/entity-type-list.component.html | 9 ++- .../entity/entity-type-list.component.ts | 57 ++++++++++----- .../components/help-popup.component.html | 10 +-- .../components/help-popup.component.scss | 18 +++++ .../shared/components/help-popup.component.ts | 6 ++ .../relation-type-autocomplete.component.html | 3 +- .../relation-type-autocomplete.component.ts | 24 ++++--- .../string-items-list.component.html | 10 ++- .../assets/locale/locale.constant-en_US.json | 1 + ui-ngx/src/form.scss | 14 +++- ui-ngx/src/styles.scss | 6 ++ 19 files changed, 206 insertions(+), 142 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html index b052a1816c..1f4d6f9377 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html @@ -15,49 +15,47 @@ limitations under the License. --> -
-
-
-
+
+
+
{{ 'relation.type' | translate }}
+
{{ 'entity.entity-types' | translate }}
+
+
+
+
-
-
- - - - -
- +
+
+
+ relation.any-relation
-
- relation.any-relation +
+
-
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss index a2d4232f24..648076be4c 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss +++ b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss @@ -14,43 +14,15 @@ * limitations under the License. */ :host { - .tb-relation-filters { - max-width: calc(100vw - 48px); - margin-top: 2px; - overflow: hidden; - - .container{ - width: 100%; - } - - .map-label { - font-weight: 400; - font-size: 12px; - } - - .body { - max-height: 363px; - overflow: auto; - - .row { - padding-top: 5px; - - .input-block { - border: 1px solid #E0E0E0; - width: 100%; - border-radius: 6px; - padding: 24px; - align-items: center; - } - } - } + .flex-50 { + flex: 1 1 50%; + } - .any-filter{ - margin: 10px 0 20px; - } + .actions-header { + width: 40px + } - .add-button { - margin: 5px 0px 15px; - } + .entity-type-list { + display: flex; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html index 63f936195a..05275e0a70 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+
{{ 'datakey.timeseries' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html index 0b3ba50927..4c2c9a834a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html @@ -18,7 +18,7 @@
{{ panelTitle }}
-
+
datakey.source
datakey.key
datakey.label
@@ -32,7 +32,7 @@ [cdkDropListDisabled]="!dragEnabled" (cdkDropListDropped)="keyDrop($event)">
- + {{ label }} - +
+ +
{{ subtypeListEmptyText | translate }} diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts index 1442b44cfd..a10bf7e3e2 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts @@ -15,7 +15,7 @@ /// import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { Observable, Subscription, throwError } from 'rxjs'; import { map, mergeMap, publishReplay, refCount, share } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -23,14 +23,15 @@ import { AppState } from '@app/core/core.state'; import { TranslateService } from '@ngx-translate/core'; import { EntitySubtype, EntityType } from '@shared/models/entity-type.models'; import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; -import { MatChipInputEvent, MatChipGrid } from '@angular/material/chips'; -import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { MatChipGrid, MatChipInputEvent } from '@angular/material/chips'; import { AssetService } from '@core/http/asset.service'; import { DeviceService } from '@core/http/device.service'; import { EdgeService } from '@core/http/edge.service'; import { EntityViewService } from '@core/http/entity-view.service'; import { BroadcastService } from '@core/services/broadcast.service'; import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { FloatLabelType } from '@angular/material/form-field'; @Component({ selector: 'tb-entity-subtype-list', @@ -46,32 +47,43 @@ import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; }) export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy { - entitySubtypeListFormGroup: UntypedFormGroup; + entitySubtypeListFormGroup: FormGroup; modelValue: Array | null; private requiredValue: boolean; + get required(): boolean { return this.requiredValue; } - @Input() label: string; - @Input() + @coerceBoolean() set required(value: boolean) { - const newVal = coerceBooleanProperty(value); - if (this.requiredValue !== newVal) { - this.requiredValue = newVal; + if (this.requiredValue !== value) { + this.requiredValue = value; this.updateValidators(); } } + @Input() + floatLabel: FloatLabelType = 'auto'; + + @Input() + label: string; + @Input() disabled: boolean; @Input() entityType: EntityType; + @Input() + emptyInputPlaceholder: string; + + @Input() + filledInputPlaceholder: string; + @ViewChild('entitySubtypeInput') entitySubtypeInput: ElementRef; @ViewChild('entitySubtypeAutocomplete') entitySubtypeAutocomplete: MatAutocomplete; @ViewChild('chipList', {static: true}) chipList: MatChipGrid; @@ -102,13 +114,14 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, private deviceService: DeviceService, private edgeService: EdgeService, private entityViewService: EntityViewService, - private fb: UntypedFormBuilder) { + private fb: FormBuilder) { this.entitySubtypeListFormGroup = this.fb.group({ entitySubtypeList: [this.entitySubtypeList, this.required ? [Validators.required] : []], entitySubtype: [null] }); } + updateValidators() { this.entitySubtypeListFormGroup.get('entitySubtypeList').setValidators(this.required ? [Validators.required] : []); this.entitySubtypeListFormGroup.get('entitySubtypeList').updateValueAndValidity(); @@ -122,7 +135,6 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, } ngOnInit() { - switch (this.entityType) { case EntityType.ASSET: this.placeholder = this.required ? this.translate.instant('asset.enter-asset-type') @@ -166,6 +178,13 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, break; } + if (this.emptyInputPlaceholder) { + this.placeholder = this.emptyInputPlaceholder; + } + if (this.filledInputPlaceholder) { + this.secondaryPlaceholder = this.filledInputPlaceholder; + } + this.filteredEntitySubtypeList = this.entitySubtypeListFormGroup.get('entitySubtype').valueChanges .pipe( map(value => value ? value : ''), @@ -225,13 +244,6 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, } this.clear(''); } - - clearChipGrid() { - this.entitySubtypeList = []; - this.modelValue = null; - this.entitySubtypeListFormGroup.get('entitySubtypeList').patchValue([], {emitEvent: true}); - } - remove(entitySubtype: string) { const index = this.entitySubtypeList.indexOf(entitySubtype); if (index >= 0) { diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.html b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.html index 0503ce836b..e9168d1f29 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.html @@ -15,7 +15,11 @@ limitations under the License. --> - + {{ label }} +
+ +
{{ 'entity.entity-type-list-empty' | translate }} diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts index 17f908b2e0..7bb52d0948 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts @@ -15,7 +15,7 @@ /// import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; -import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Observable, of } from 'rxjs'; import { filter, map, mergeMap, share, tap } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -25,9 +25,8 @@ import { AliasEntityType, EntityType, entityTypeTranslations } from '@shared/mod import { EntityService } from '@core/http/entity.service'; import { MatAutocomplete } from '@angular/material/autocomplete'; import { MatChipGrid } from '@angular/material/chips'; -import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { FloatLabelType, SubscriptSizing } from '@angular/material/form-field'; -import { coerceBoolean } from '@shared/decorators/coercion'; +import { FloatLabelType, MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; +import { coerceArray, coerceBoolean } from '@shared/decorators/coercion'; interface EntityTypeInfo { name: string; @@ -48,7 +47,7 @@ interface EntityTypeInfo { }) export class EntityTypeListComponent implements ControlValueAccessor, OnInit, AfterViewInit { - entityTypeListFormGroup: UntypedFormGroup; + entityTypeListFormGroup: FormGroup; modelValue: Array | null; @@ -57,19 +56,28 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af return this.requiredValue; } - @Input() label: string; - - @Input() floatLabel: FloatLabelType = 'auto'; - @Input() + @coerceBoolean() set required(value: boolean) { - const newVal = coerceBooleanProperty(value); - if (this.requiredValue !== newVal) { - this.requiredValue = newVal; + if (this.requiredValue !== value) { + this.requiredValue = value; this.updateValidators(); } } + @Input() + @coerceArray() + additionalClasses: Array; + + @Input() + appearance: MatFormFieldAppearance = 'fill'; + + @Input() + label: string; + + @Input() + floatLabel: FloatLabelType = 'auto'; + @Input() disabled: boolean; @@ -79,6 +87,12 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af @Input() allowedEntityTypes: Array; + @Input() + emptyInputPlaceholder: string; + + @Input() + filledInputPlaceholder: string; + @Input() @coerceBoolean() ignoreAuthorityFilter: boolean; @@ -103,7 +117,7 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af constructor(private store: Store, public translate: TranslateService, private entityService: EntityService, - private fb: UntypedFormBuilder) { + private fb: FormBuilder) { this.entityTypeListFormGroup = this.fb.group({ entityTypeList: [this.entityTypeList, this.required ? [Validators.required] : []], entityType: [null] @@ -123,11 +137,17 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af } ngOnInit() { - - this.placeholder = this.required ? this.translate.instant('entity.enter-entity-type') - : this.translate.instant('entity.any-entity'); - this.secondaryPlaceholder = '+' + this.translate.instant('entity.entity-type'); - + if (this.emptyInputPlaceholder) { + this.placeholder = this.emptyInputPlaceholder; + } else { + this.placeholder = this.required ? this.translate.instant('entity.enter-entity-type') : + this.translate.instant('entity.any-entity'); + } + if (this.filledInputPlaceholder) { + this.secondaryPlaceholder = this.filledInputPlaceholder; + } else { + this.secondaryPlaceholder = '+' + this.translate.instant('entity.entity-type'); + } let entityTypes: Array; if (this.ignoreAuthorityFilter && this.allowedEntityTypes && this.allowedEntityTypes.length) { @@ -250,5 +270,4 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af this.entityTypeInput.nativeElement.focus(); }, 0); } - } diff --git a/ui-ngx/src/app/shared/components/help-popup.component.html b/ui-ngx/src/app/shared/components/help-popup.component.html index 1a9fe5541a..730d054b8b 100644 --- a/ui-ngx/src/app/shared/components/help-popup.component.html +++ b/ui-ngx/src/app/shared/components/help-popup.component.html @@ -30,19 +30,21 @@
-
+
diff --git a/ui-ngx/src/app/shared/components/help-popup.component.scss b/ui-ngx/src/app/shared/components/help-popup.component.scss index 2ec26a3c0f..6be4d9b71c 100644 --- a/ui-ngx/src/app/shared/components/help-popup.component.scss +++ b/ui-ngx/src/app/shared/components/help-popup.component.scss @@ -17,6 +17,9 @@ width: initial; display: inline-block; vertical-align: middle; + &.hint-button { + line-height: 1; + } } .tb-help-popup-button { @@ -65,4 +68,19 @@ vertical-align: middle; } } + &.hint-button { + padding: 2px 3px; + line-height: 1; + &.mat-mdc-outlined-button { + padding: 1px 2px; + } + .mdc-button__label > span { + .mat-icon { + margin-right: 0; + } + .mat-mdc-progress-spinner { + margin-right: 0; + } + } + } } diff --git a/ui-ngx/src/app/shared/components/help-popup.component.ts b/ui-ngx/src/app/shared/components/help-popup.component.ts index 08be61f728..64722348a2 100644 --- a/ui-ngx/src/app/shared/components/help-popup.component.ts +++ b/ui-ngx/src/app/shared/components/help-popup.component.ts @@ -28,6 +28,7 @@ import { TbPopoverService } from '@shared/components/popover.service'; import { PopoverPlacement } from '@shared/components/popover.models'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { isDefinedAndNotNull } from '@core/utils'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ // eslint-disable-next-line @angular-eslint/component-selector @@ -62,6 +63,11 @@ export class HelpPopupComponent implements OnChanges, OnDestroy { popoverVisible = false; popoverReady = true; + + @Input() + @coerceBoolean() + hintMode = false; + triggerSafeHtml: SafeHtml = null; textMode = false; diff --git a/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html b/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html index c057f1df09..99a650098e 100644 --- a/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html @@ -15,7 +15,8 @@ limitations under the License. --> - + {{ label }} ; - @Input() floatLabel: FloatLabelType = 'auto'; + @Input() + appearance: MatFormFieldAppearance = 'fill'; @Input() - set required(value: boolean) { - this.requiredValue = coerceBooleanProperty(value); - } + floatLabel: FloatLabelType = 'auto'; + + @Input() + @coerceBoolean() + required: boolean; @Input() disabled: boolean; 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 4677cbc3de..7416e96870 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 @@ -54,7 +54,15 @@ {{ 'common.not-found' | translate }} - {{ hint }} + + {{ hint }} + + + + +
+ +
{{ requiredText }} 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 f90a34fe8e..60213184eb 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2031,6 +2031,7 @@ "entity-types": "Entity types", "entity-type-list": "Entity type list", "any-entity": "Any entity", + "add-entity-type": "Add entity type", "enter-entity-type": "Enter entity type", "no-entities-matching": "No entities matching '{{entity}}' were found.", "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index bef8bf621e..79f141e9d5 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -307,6 +307,10 @@ } } &.tb-chips { + &.flex { + flex: 1; + width: auto; + } .mat-mdc-text-field-wrapper { &.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) { .mat-mdc-form-field-infix { @@ -357,7 +361,7 @@ } .tb-prompt { - height: 38px; + height: 40px; } } @@ -366,11 +370,19 @@ flex-direction: row; gap: 8px; padding-left: 8px; + padding-right: 8px; place-content: center flex-start; align-items: center; + &.no-padding-right { + padding-right: 0; + } @media #{$mat-gt-md} { gap: 12px; padding-left: 12px; + padding-right: 12px; + &.no-padding-right { + padding-right: 0; + } } &-cell { font-weight: 400; diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index ec6060823d..8e6a9dde75 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -855,6 +855,9 @@ mat-label { svg { vertical-align: inherit; } + &.tb-mat-12 { + @include tb-mat-icon-size(12); + } &.tb-mat-16 { @include tb-mat-icon-size(16); } @@ -1208,4 +1211,7 @@ mat-label { color: inherit; } + .cursor-pointer { + cursor: pointer; + } } From b38165e7455e8761f31e1036f73473483c68c055 Mon Sep 17 00:00:00 2001 From: kalytka Date: Thu, 10 Aug 2023 18:20:58 +0300 Subject: [PATCH 37/51] Add translation --- .../home/components/relation/relation-filters.component.html | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html index 1f4d6f9377..57c5ecf621 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html @@ -42,7 +42,7 @@ mat-icon-button (click)="removeFilter($index)" [disabled]="isLoading$ | async" - matTooltip="{{ 'tb.key-val.remove-mapping-entry' | translate }}" + matTooltip="{{ 'relation.remove-filter' | translate }}" matTooltipPosition="above"> delete 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 60213184eb..ecad69fe24 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3326,6 +3326,7 @@ "delete-from-relations-title": "Are you sure you want to delete { count, plural, =1 {1 relation} other {# relations} }?", "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.", "remove-relation-filter": "Remove relation filter", + "remove-filter": "Remove filter", "add-relation-filter": "Add relation filter", "any-relation": "Any relation", "relation-filters": "Relation filters", From 7de5e6b08491feb584386f21d70144c459d27a9d Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 11 Aug 2023 12:49:32 +0300 Subject: [PATCH 38/51] updated default config for math node --- .../rule/engine/math/TbMathNodeConfiguration.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java index 1636898b8c..f4fccd3ae6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java @@ -32,9 +32,10 @@ public class TbMathNodeConfiguration implements NodeConfiguration Date: Fri, 11 Aug 2023 16:15:34 +0300 Subject: [PATCH 39/51] replace x with t --- .../thingsboard/rule/engine/math/TbMathNodeConfiguration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java index f4fccd3ae6..e1a3523cf0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java @@ -33,8 +33,8 @@ public class TbMathNodeConfiguration implements NodeConfiguration Date: Fri, 11 Aug 2023 19:23:51 +0300 Subject: [PATCH 40/51] PROD-2339: fix getFeatureType method to handle RPC server-side response over DTLS --- .../transport/coap/CoapTransportResource.java | 11 +- .../coap/CoapTransportResourceTest.java | 352 ++++++++++++++++++ 2 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index d6958137c7..bd02d9fcc4 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -30,6 +30,7 @@ import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; @@ -379,12 +380,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } - private Optional getFeatureType(Request request) { + protected Optional getFeatureType(Request request) { List uriPath = request.getOptions().getUriPath(); try { - if (uriPath.size() >= FEATURE_TYPE_POSITION) { + int size = uriPath.size(); + if (size >= FEATURE_TYPE_POSITION) { + if (size == FEATURE_TYPE_POSITION && StringUtils.isNumeric(uriPath.get(size - 1))) { + return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 2).toUpperCase())); + } return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); - } else if (uriPath.size() >= FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) { + } else if (size == FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) { if (uriPath.contains(DataConstants.PROVISION)) { return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase())); } diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java new file mode 100644 index 0000000000..666f6c95df --- /dev/null +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -0,0 +1,352 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap; + +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.OptionSet; +import org.eclipse.californium.core.coap.Request; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.thingsboard.server.coapserver.CoapServerService; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; +import org.thingsboard.server.transport.coap.client.CoapClientContext; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class CoapTransportResourceTest { + + private static final String V1 = "v1"; + private static final String API = "api"; + private static final String TELEMETRY = "telemetry"; + private static final String ATTRIBUTES = "attributes"; + private static final String RPC = "rpc"; + private static final String CLAIM = "claim"; + private static final String PROVISION = "provision"; + private static final String GET_ATTRIBUTES_URI_QUERY = "clientKeys=attribute1,attribute2&sharedKeys=shared1,shared2"; + + private static final Random RANDOM = new Random(); + + private CoapTransportResource coapTransportResource; + + @BeforeEach + void setUp() { + + var ctxMock = mock(CoapTransportContext.class); + var coapServerServiceMock = mock(CoapServerService.class); + var transportServiceMock = mock(TransportService.class); + var clientContextMock = mock(CoapClientContext.class); + var schedulerComponentMock = mock(SchedulerComponent.class); + + when(ctxMock.getTransportService()).thenReturn(transportServiceMock); + when(ctxMock.getClientContext()).thenReturn(clientContextMock); + when(ctxMock.getSessionReportTimeout()).thenReturn(1L); + when(ctxMock.getScheduler()).thenReturn(schedulerComponentMock); + + coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); + } + + @AfterEach + void tearDown() { + } + + // accessToken based tests + + @Test + void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { + // GIVEN + var request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), TELEMETRY); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toGetAttributesAccessTokenRequest(StringUtils.randomAlphanumeric(20)); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), RPC); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toRpcResponseAccessTokenRequest(StringUtils.randomAlphanumeric(20), RANDOM.nextInt(100)); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), RPC); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), CLAIM); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); + } + + // certificate based tests + + @Test + void givenPostTelemetryCertificateRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.POST, TELEMETRY); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenPostAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.POST, ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenGetAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toGetAttributesCertificateRequest(); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForAttributesUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.GET, ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForRpcUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.GET, RPC); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toRpcResponseCertificateRequest(RANDOM.nextInt(100)); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClientSideRpcCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, RPC); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClaimingCertificateRequest_whenGetFeatureType_thenFeatureTypeClaim() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, CLAIM); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); + } + + // provision request + + @Test + void givenProvisionRequest_whenGetFeatureType_thenFeatureTypeProvision() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, PROVISION); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); + } + + private Request toAccessTokenRequest(CoAP.Code method, String accessToken, String featureType) { + return getAccessTokenRequest(method, accessToken, featureType, null, null); + } + + private Request toGetAttributesAccessTokenRequest(String accessToken) { + return getAccessTokenRequest(CoAP.Code.GET, accessToken, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + } + + private Request toRpcResponseAccessTokenRequest(String accessToken, Integer requestId) { + return getAccessTokenRequest(CoAP.Code.POST, accessToken, CoapTransportResourceTest.RPC, requestId, null); + } + + private Request toCertificateRequest(CoAP.Code method, String featureType) { + return getCertificateRequest(method, featureType, null, null); + } + + private Request toGetAttributesCertificateRequest() { + return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + } + + private Request toRpcResponseCertificateRequest(Integer requestId) { + return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, requestId, null); + } + + private Request getAccessTokenRequest(CoAP.Code method, String accessToken, String featureType, Integer requestId, String uriQuery) { + var request = new Request(method); + var options = new OptionSet(); + options.addUriPath(API); + options.addUriPath(V1); + options.addUriPath(accessToken); + options.addUriPath(featureType); + if (requestId != null) { + options.addUriPath(String.valueOf(requestId)); + } + if (uriQuery != null) { + options.setUriQuery(uriQuery); + } + request.setOptions(options); + return request; + } + + private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + var request = new Request(method); + var options = new OptionSet(); + options.addUriPath(API); + options.addUriPath(V1); + options.addUriPath(featureType); + if (requestId != null) { + options.addUriPath(String.valueOf(requestId)); + } + if (uriQuery != null) { + options.setUriQuery(uriQuery); + } + request.setOptions(options); + return request; + } + + +} From cebe1040d4f25b11fd9d3613fd3c15e81c6a9359 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 11 Aug 2023 20:27:54 +0300 Subject: [PATCH 41/51] refactoring after review --- .../server/common/data/msg/TbMsgType.java | 30 ++------ .../server/common/data/msg/TbMsgTypeTest.java | 22 +----- .../thingsboard/server/common/msg/TbMsg.java | 76 ++++++------------- .../engine/filter/TbMsgTypeSwitchNode.java | 3 +- .../filter/TbMsgTypeSwitchNodeTest.java | 2 +- 5 files changed, 35 insertions(+), 98 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java index bd351ffecd..f7c9a5b05f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -16,11 +16,10 @@ package org.thingsboard.server.common.data.msg; import lombok.Getter; +import org.thingsboard.server.common.data.StringUtils; -import java.util.Arrays; import java.util.EnumSet; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; public enum TbMsgType { @@ -79,12 +78,12 @@ public enum TbMsgType { MSG_COUNT_SELF_MSG(null, true), // Custom or N/A type: - CUSTOM_OR_NA_TYPE(null, false, true); + CUSTOM_OR_NA_TYPE(null, false); public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() .filter(tbMsgType -> !tbMsgType.isTellSelfOnly()) .map(TbMsgType::getRuleNodeConnection) - .filter(Objects::nonNull) + .filter(connection -> !TbNodeConnectionType.OTHER.equals(connection)) .collect(Collectors.toUnmodifiableList()); @Getter @@ -93,32 +92,13 @@ public enum TbMsgType { @Getter private final boolean tellSelfOnly; - @Getter - private final boolean customType; - - TbMsgType(String ruleNodeConnection, boolean tellSelfOnly, boolean customType) { - this.ruleNodeConnection = ruleNodeConnection; - this.tellSelfOnly = tellSelfOnly; - this.customType = customType; - } - TbMsgType(String ruleNodeConnection, boolean tellSelfOnly) { - this.ruleNodeConnection = ruleNodeConnection; + this.ruleNodeConnection = StringUtils.isNotEmpty(ruleNodeConnection) ? ruleNodeConnection : TbNodeConnectionType.OTHER; this.tellSelfOnly = tellSelfOnly; - this.customType = false; } TbMsgType(String ruleNodeConnection) { - this.ruleNodeConnection = ruleNodeConnection; - this.tellSelfOnly = false; - this.customType = false; - } - - public static String getRuleNodeConnectionOrElseOther(TbMsgType msgType) { - if (msgType == null || msgType.isCustomType() || msgType.isTellSelfOnly()) { - return TbNodeConnectionType.OTHER; - } - return Objects.requireNonNullElse(msgType.getRuleNodeConnection(), TbNodeConnectionType.OTHER); + this(ruleNodeConnection, false); } } diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java index ff41505266..c77d109814 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java @@ -63,39 +63,25 @@ class TbMsgTypeTest { var tbMsgTypes = TbMsgType.values(); for (var type : tbMsgTypes) { if (typesWithNullRuleNodeConnection.contains(type)) { - assertThat(type.getRuleNodeConnection()).isNull(); + assertThat(type.getRuleNodeConnection()).isEqualTo(TbNodeConnectionType.OTHER); } else { - assertThat(type.getRuleNodeConnection()).isNotNull(); + assertThat(type.getRuleNodeConnection()).isNotEqualTo(TbNodeConnectionType.OTHER); } } } @Test void getRuleNodeConnectionOrElseOtherTest() { - assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(null)) - .isEqualTo(TbNodeConnectionType.OTHER); var tbMsgTypes = TbMsgType.values(); for (var type : tbMsgTypes) { if (typesWithNullRuleNodeConnection.contains(type)) { - assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(type)) + assertThat(type.getRuleNodeConnection()) .isEqualTo(TbNodeConnectionType.OTHER); } else { - assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(type)).isNotNull() + assertThat(type.getRuleNodeConnection()).isNotNull() .isNotEqualTo(TbNodeConnectionType.OTHER); } } } - @Test - void getCustomTypeTest() { - var tbMsgTypes = TbMsgType.values(); - for (var type : tbMsgTypes) { - if (type.equals(CUSTOM_OR_NA_TYPE)) { - assertThat(type.isCustomType()).isTrue(); - continue; - } - assertThat(type.isCustomType()).isFalse(); - } - } - } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index b4f6ccd584..afd3d0268c 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -98,7 +98,7 @@ public final class TbMsg implements Serializable { */ @Deprecated(since = "3.5.2") public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { - return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, + return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); } @@ -109,7 +109,7 @@ public final class TbMsg implements Serializable { @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { - return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); } @@ -171,13 +171,13 @@ public final class TbMsg implements Serializable { */ @Deprecated(since = "3.5.2") public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { - return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, + return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); } @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { - return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, customerId, metaData.copy(), dataType, data, null, null, null, TbMsgCallback.EMPTY); } @@ -223,13 +223,13 @@ public final class TbMsg implements Serializable { @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { - return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, null, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); } @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) { - return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, null, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, callback); } @@ -251,7 +251,7 @@ public final class TbMsg implements Serializable { */ @Deprecated(since = "3.5.2") public static TbMsg transformMsg(TbMsg tbMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { - return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type, originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, null, type, originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.callback); } @@ -271,82 +271,57 @@ public final class TbMsg implements Serializable { } public static TbMsg transformMsgOriginator(TbMsg tbMsg, EntityId originatorId) { - return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, originatorId, tbMsg.getCustomerId(), tbMsg.metaData, tbMsg.dataType, + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, originatorId, tbMsg.getCustomerId(), tbMsg.metaData, tbMsg.dataType, tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } public static TbMsg transformMsgData(TbMsg tbMsg, String data) { - return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } public static TbMsg transformMsgMetadata(TbMsg tbMsg, TbMsgMetaData metadata) { - return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata.copy(), tbMsg.dataType, + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata.copy(), tbMsg.dataType, tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } public static TbMsg transformMsg(TbMsg tbMsg, TbMsgMetaData metadata, String data) { - return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata, tbMsg.dataType, + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata, tbMsg.dataType, data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } public static TbMsg transformMsgCustomerId(TbMsg tbMsg, CustomerId customerId) { - return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType, + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } public static TbMsg transformMsgRuleChainId(TbMsg tbMsg, RuleChainId ruleChainId) { - return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, ruleChainId, null, tbMsg.ctx.copy(), tbMsg.getCallback()); } public static TbMsg transformMsgQueueName(TbMsg tbMsg, String queueName) { - return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, + return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, tbMsg.getRuleChainId(), null, tbMsg.ctx.copy(), tbMsg.getCallback()); } public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId, String queueName) { - return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, + return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, ruleChainId, null, tbMsg.ctx.copy(), tbMsg.getCallback()); } //used for enqueueForTellNext public static TbMsg newMsg(TbMsg tbMsg, String queueName, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { - return new TbMsg(queueName, UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.customerId, tbMsg.getMetaData().copy(), + return new TbMsg(queueName, UUID.randomUUID(), tbMsg.getTs(), tbMsg.getInternalType(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.customerId, tbMsg.getMetaData().copy(), tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, tbMsg.ctx.copy(), TbMsgCallback.EMPTY); } private TbMsg(String queueName, UUID id, long ts, TbMsgType internalType, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgProcessingCtx ctx, TbMsgCallback callback) { - this.id = id; - this.queueName = queueName; - if (ts > 0) { - this.ts = ts; - } else { - this.ts = System.currentTimeMillis(); - } - this.internalType = internalType; - this.type = internalType.name(); - this.originator = originator; - if (customerId == null || customerId.isNullUid()) { - if (originator != null && originator.getEntityType() == EntityType.CUSTOMER) { - this.customerId = (CustomerId) originator; - } else { - this.customerId = null; - } - } else { - this.customerId = customerId; - } - this.metaData = metaData; - this.dataType = dataType; - this.data = data; - this.ruleChainId = ruleChainId; - this.ruleNodeId = ruleNodeId; - this.ctx = ctx != null ? ctx : new TbMsgProcessingCtx(); - this.callback = Objects.requireNonNullElse(callback, TbMsgCallback.EMPTY); + this(queueName, id, ts, internalType, internalType.name(), originator, customerId, metaData, dataType, data, ruleChainId, ruleNodeId, ctx, callback); } - private TbMsg(String queueName, UUID id, long ts, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data, + private TbMsg(String queueName, UUID id, long ts, TbMsgType internalType, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgProcessingCtx ctx, TbMsgCallback callback) { this.id = id; this.queueName = queueName; @@ -356,7 +331,7 @@ public final class TbMsg implements Serializable { this.ts = System.currentTimeMillis(); } this.type = type; - this.internalType = getInternalType(); + this.internalType = internalType != null ? internalType : getInternalType(type); this.originator = originator; if (customerId == null || customerId.isNullUid()) { if (originator != null && originator.getEntityType() == EntityType.CUSTOMER) { @@ -442,7 +417,7 @@ public final class TbMsg implements Serializable { } TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()]; - return new TbMsg(queueName, UUID.fromString(proto.getId()), proto.getTs(), proto.getType(), entityId, customerId, + return new TbMsg(queueName, UUID.fromString(proto.getId()), proto.getTs(), null, proto.getType(), entityId, customerId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, ctx, callback); } catch (InvalidProtocolBufferException e) { throw new IllegalStateException("Could not parse protobuf for TbMsg", e); @@ -454,17 +429,17 @@ public final class TbMsg implements Serializable { } public TbMsg copyWithRuleChainId(RuleChainId ruleChainId, UUID msgId) { - return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId, + return new TbMsg(this.queueName, msgId, this.ts, this.internalType, this.type, this.originator, this.customerId, this.metaData, this.dataType, this.data, ruleChainId, null, this.ctx, callback); } public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId, UUID msgId) { - return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId, + return new TbMsg(this.queueName, msgId, this.ts, this.internalType, this.type, this.originator, this.customerId, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ctx, callback); } public TbMsg copyWithNewCtx() { - return new TbMsg(this.queueName, this.id, this.ts, this.type, this.originator, this.customerId, + return new TbMsg(this.queueName, this.id, this.ts, this.internalType, this.type, this.originator, this.customerId, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ctx.copy(), TbMsgCallback.EMPTY); } @@ -500,10 +475,7 @@ public final class TbMsg implements Serializable { return ts; } - public TbMsgType getInternalType() { - if (internalType != null) { - return internalType; - } + private TbMsgType getInternalType(String type) { try { return TbMsgType.valueOf(type); } catch (IllegalArgumentException e) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index 068d342ea7..d5b06b4537 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -50,7 +49,7 @@ public class TbMsgTypeSwitchNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ctx.tellNext(msg, TbMsgType.getRuleNodeConnectionOrElseOther(msg.getInternalType())); + ctx.tellNext(msg, msg.getInternalType().getRuleNodeConnection()); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java index 7861b2f489..603c23cd05 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java @@ -84,7 +84,7 @@ class TbMsgTypeSwitchNodeTest { assertThat(msg.getType()).isEqualTo(msg.getInternalType().name()); assertThat(msg).isSameAs(tbMsgList.get(i)); assertThat(resultNodeConnections.get(i)) - .isEqualTo(TbMsgType.getRuleNodeConnectionOrElseOther(msg.getInternalType())); + .isEqualTo(msg.getInternalType().getRuleNodeConnection()); } } From ea5a8552723e09f333be10d9b5785e4b20acd044 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 11 Aug 2023 20:34:17 +0300 Subject: [PATCH 42/51] renamed custom msg type to NA --- .../org/thingsboard/server/common/data/msg/TbMsgType.java | 2 +- .../org/thingsboard/server/common/data/msg/TbMsgTypeTest.java | 4 ++-- .../main/java/org/thingsboard/server/common/msg/TbMsg.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java index f7c9a5b05f..1f7691c7f5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -78,7 +78,7 @@ public enum TbMsgType { MSG_COUNT_SELF_MSG(null, true), // Custom or N/A type: - CUSTOM_OR_NA_TYPE(null, false); + NA(null, false); public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() .filter(tbMsgType -> !tbMsgType.isTellSelfOnly()) diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java index c77d109814..870d5a2804 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java @@ -22,7 +22,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM; import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; -import static org.thingsboard.server.common.data.msg.TbMsgType.CUSTOM_OR_NA_TYPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.NA; import static org.thingsboard.server.common.data.msg.TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.DELAY_TIMEOUT_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED_TO_EDGE; @@ -53,7 +53,7 @@ class TbMsgTypeTest { DEDUPLICATION_TIMEOUT_SELF_MSG, DELAY_TIMEOUT_SELF_MSG, MSG_COUNT_SELF_MSG, - CUSTOM_OR_NA_TYPE + NA ); // backward-compatibility tests diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index afd3d0268c..8b76677c76 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -479,7 +479,7 @@ public final class TbMsg implements Serializable { try { return TbMsgType.valueOf(type); } catch (IllegalArgumentException e) { - return TbMsgType.CUSTOM_OR_NA_TYPE; + return TbMsgType.NA; } } From e1b18e7bed022d0a54680946cd124fe2bf2f6f59 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Sat, 12 Aug 2023 09:17:21 +0300 Subject: [PATCH 43/51] additional updates after review --- .../server/common/data/msg/TbMsgType.java | 20 +++++++++++-------- .../thingsboard/server/common/msg/TbMsg.java | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java index 1f7691c7f5..23149c79c1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -38,10 +38,10 @@ public enum TbMsgType { ENTITY_UNASSIGNED("Entity Unassigned"), ATTRIBUTES_UPDATED("Attributes Updated"), ATTRIBUTES_DELETED("Attributes Deleted"), - ALARM(null), + ALARM, ALARM_ACK("Alarm Acknowledged"), ALARM_CLEAR("Alarm Cleared"), - ALARM_DELETE(null), + ALARM_DELETE, ALARM_ASSIGNED("Alarm Assigned"), ALARM_UNASSIGNED("Alarm Unassigned"), COMMENT_CREATED("Comment Created"), @@ -49,8 +49,8 @@ public enum TbMsgType { RPC_CALL_FROM_SERVER_TO_DEVICE("RPC Request to Device"), ENTITY_ASSIGNED_FROM_TENANT("Entity Assigned From Tenant"), ENTITY_ASSIGNED_TO_TENANT("Entity Assigned To Tenant"), - ENTITY_ASSIGNED_TO_EDGE(null), - ENTITY_UNASSIGNED_FROM_EDGE(null), + ENTITY_ASSIGNED_TO_EDGE, + ENTITY_UNASSIGNED_FROM_EDGE, TIMESERIES_UPDATED("Timeseries Updated"), TIMESERIES_DELETED("Timeseries Deleted"), RPC_QUEUED("RPC Queued"), @@ -64,9 +64,9 @@ public enum TbMsgType { RELATION_ADD_OR_UPDATE("Relation Added or Updated"), RELATION_DELETED("Relation Deleted"), RELATIONS_DELETED("All Relations Deleted"), - PROVISION_SUCCESS(null), - PROVISION_FAILURE(null), - SEND_EMAIL(null), + PROVISION_SUCCESS, + PROVISION_FAILURE, + SEND_EMAIL, // tellSelfOnly types GENERATOR_NODE_SELF_MSG(null, true), @@ -78,7 +78,7 @@ public enum TbMsgType { MSG_COUNT_SELF_MSG(null, true), // Custom or N/A type: - NA(null, false); + NA; public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() .filter(tbMsgType -> !tbMsgType.isTellSelfOnly()) @@ -101,4 +101,8 @@ public enum TbMsgType { this(ruleNodeConnection, false); } + TbMsgType() { + this(null, false); + } + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index 8b76677c76..63c1e27385 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -484,7 +484,7 @@ public final class TbMsg implements Serializable { } public boolean isTypeOf(TbMsgType tbMsgType) { - return tbMsgType != null && tbMsgType.equals(getInternalType()); + return internalType.equals(tbMsgType); } public boolean isTypeOneOf(TbMsgType... types) { From f647fca59c61130aa5f16d2691d404c10aa5be19 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Sat, 12 Aug 2023 09:38:49 +0300 Subject: [PATCH 44/51] refactoring of test base --- .../coap/CoapTransportResourceTest.java | 71 ++++++++----------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java index 666f6c95df..2e5b367e4c 100644 --- a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -18,7 +18,9 @@ package org.thingsboard.server.transport.coap; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.OptionSet; import org.eclipse.californium.core.coap.Request; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.thingsboard.server.coapserver.CoapServerService; @@ -48,10 +50,10 @@ class CoapTransportResourceTest { private static final Random RANDOM = new Random(); - private CoapTransportResource coapTransportResource; + private static CoapTransportResource coapTransportResource; - @BeforeEach - void setUp() { + @BeforeAll + static void setUp() { var ctxMock = mock(CoapTransportContext.class); var coapServerServiceMock = mock(CoapServerService.class); @@ -67,16 +69,12 @@ class CoapTransportResourceTest { coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); } - @AfterEach - void tearDown() { - } - // accessToken based tests @Test void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { // GIVEN - var request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), TELEMETRY); + var request = toAccessTokenRequest(CoAP.Code.POST, TELEMETRY); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -89,7 +87,7 @@ class CoapTransportResourceTest { @Test void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + Request request = toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -102,7 +100,7 @@ class CoapTransportResourceTest { @Test void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toGetAttributesAccessTokenRequest(StringUtils.randomAlphanumeric(20)); + Request request = toGetAttributesAccessTokenRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -114,7 +112,7 @@ class CoapTransportResourceTest { @Test void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + Request request = toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -126,7 +124,7 @@ class CoapTransportResourceTest { @Test void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), RPC); + Request request = toAccessTokenRequest(CoAP.Code.GET, RPC); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -138,7 +136,7 @@ class CoapTransportResourceTest { @Test void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toRpcResponseAccessTokenRequest(StringUtils.randomAlphanumeric(20), RANDOM.nextInt(100)); + Request request = toRpcResponseAccessTokenRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -150,7 +148,7 @@ class CoapTransportResourceTest { @Test void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), RPC); + Request request = toAccessTokenRequest(CoAP.Code.POST, RPC); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -162,7 +160,7 @@ class CoapTransportResourceTest { @Test void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), CLAIM); + Request request = toAccessTokenRequest(CoAP.Code.POST, CLAIM); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -241,7 +239,7 @@ class CoapTransportResourceTest { @Test void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toRpcResponseCertificateRequest(RANDOM.nextInt(100)); + Request request = toRpcResponseCertificateRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -291,16 +289,16 @@ class CoapTransportResourceTest { assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); } - private Request toAccessTokenRequest(CoAP.Code method, String accessToken, String featureType) { - return getAccessTokenRequest(method, accessToken, featureType, null, null); + private Request toAccessTokenRequest(CoAP.Code method, String featureType) { + return getAccessTokenRequest(method, featureType, null, null); } - private Request toGetAttributesAccessTokenRequest(String accessToken) { - return getAccessTokenRequest(CoAP.Code.GET, accessToken, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + private Request toGetAttributesAccessTokenRequest() { + return getAccessTokenRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseAccessTokenRequest(String accessToken, Integer requestId) { - return getAccessTokenRequest(CoAP.Code.POST, accessToken, CoapTransportResourceTest.RPC, requestId, null); + private Request toRpcResponseAccessTokenRequest() { + return getAccessTokenRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } private Request toCertificateRequest(CoAP.Code method, String featureType) { @@ -311,32 +309,26 @@ class CoapTransportResourceTest { return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseCertificateRequest(Integer requestId) { - return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, requestId, null); + private Request toRpcResponseCertificateRequest() { + return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request getAccessTokenRequest(CoAP.Code method, String accessToken, String featureType, Integer requestId, String uriQuery) { - var request = new Request(method); - var options = new OptionSet(); - options.addUriPath(API); - options.addUriPath(V1); - options.addUriPath(accessToken); - options.addUriPath(featureType); - if (requestId != null) { - options.addUriPath(String.valueOf(requestId)); - } - if (uriQuery != null) { - options.setUriQuery(uriQuery); - } - request.setOptions(options); - return request; + private Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + return getRequest(method, featureType, false, requestId, uriQuery); } private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + return getRequest(method, featureType, true, requestId, uriQuery); + } + + private Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { var request = new Request(method); var options = new OptionSet(); options.addUriPath(API); options.addUriPath(V1); + if (!dtls) { + options.addUriPath(StringUtils.randomAlphanumeric(20)); + } options.addUriPath(featureType); if (requestId != null) { options.addUriPath(String.valueOf(requestId)); @@ -348,5 +340,4 @@ class CoapTransportResourceTest { return request; } - } From c5ff8b4229af3adfde1e5c8436540afca1d71512 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Sat, 12 Aug 2023 09:52:36 +0300 Subject: [PATCH 45/51] refactored to parameterized test --- .../coap/CoapTransportResourceTest.java | 270 +++--------------- 1 file changed, 44 insertions(+), 226 deletions(-) diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java index 2e5b367e4c..c7f33e3694 100644 --- a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -18,11 +18,10 @@ package org.thingsboard.server.transport.coap; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.OptionSet; import org.eclipse.californium.core.coap.Request; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.thingsboard.server.coapserver.CoapServerService; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.msg.session.FeatureType; @@ -31,6 +30,7 @@ import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.transport.coap.client.CoapClientContext; import java.util.Random; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -69,259 +69,77 @@ class CoapTransportResourceTest { coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); } - // accessToken based tests - - @Test - void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { - // GIVEN - var request = toAccessTokenRequest(CoAP.Code.POST, TELEMETRY); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toGetAttributesAccessTokenRequest(); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, RPC); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toRpcResponseAccessTokenRequest(); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, RPC); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, CLAIM); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); - } - - // certificate based tests - - @Test - void givenPostTelemetryCertificateRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.POST, TELEMETRY); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenPostAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.POST, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenGetAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toGetAttributesCertificateRequest(); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForAttributesUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.GET, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForRpcUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.GET, RPC); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toRpcResponseCertificateRequest(); - - // WHEN + @ParameterizedTest + @MethodSource("provideRequestAndFeatureType") + void givenRequest_whenGetFeatureType_thenReturnedExpectedFeatureType(Request request, FeatureType expectedFeatureType) { var featureTypeOptional = coapTransportResource.getFeatureType(request); - // THEN assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + assertEquals(expectedFeatureType, featureTypeOptional.get(), "Feature type is invalid"); } - @Test - void givenClientSideRpcCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, RPC); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + static Stream provideRequestAndFeatureType() { + return Stream.of( + // accessToken based tests + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, TELEMETRY), FeatureType.TELEMETRY), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toGetAttributesAccessTokenRequest(), FeatureType.ATTRIBUTES), + Arguments.of(toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toAccessTokenRequest(CoAP.Code.GET, RPC), FeatureType.RPC), + Arguments.of(toRpcResponseAccessTokenRequest(), FeatureType.RPC), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, RPC), FeatureType.RPC), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, CLAIM), FeatureType.CLAIM), + // certificate based tests + Arguments.of(toCertificateRequest(CoAP.Code.POST, TELEMETRY), FeatureType.TELEMETRY), + Arguments.of(toCertificateRequest(CoAP.Code.POST, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toGetAttributesCertificateRequest(), FeatureType.ATTRIBUTES), + Arguments.of(toCertificateRequest(CoAP.Code.GET, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toCertificateRequest(CoAP.Code.GET, RPC), FeatureType.RPC), + Arguments.of(toRpcResponseCertificateRequest(), FeatureType.RPC), + Arguments.of(toCertificateRequest(CoAP.Code.POST, RPC), FeatureType.RPC), + Arguments.of(toCertificateRequest(CoAP.Code.POST, CLAIM), FeatureType.CLAIM), + // provision request + Arguments.of(toProvisionRequest(), FeatureType.PROVISION) + ); } - @Test - void givenClaimingCertificateRequest_whenGetFeatureType_thenFeatureTypeClaim() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, CLAIM); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); - } - - // provision request - - @Test - void givenProvisionRequest_whenGetFeatureType_thenFeatureTypeProvision() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, PROVISION); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); - } - - private Request toAccessTokenRequest(CoAP.Code method, String featureType) { + private static Request toAccessTokenRequest(CoAP.Code method, String featureType) { return getAccessTokenRequest(method, featureType, null, null); } - private Request toGetAttributesAccessTokenRequest() { + private static Request toGetAttributesAccessTokenRequest() { return getAccessTokenRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseAccessTokenRequest() { + private static Request toRpcResponseAccessTokenRequest() { return getAccessTokenRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request toCertificateRequest(CoAP.Code method, String featureType) { + private static Request toCertificateRequest(CoAP.Code method, String featureType) { return getCertificateRequest(method, featureType, null, null); } - private Request toGetAttributesCertificateRequest() { + private static Request toGetAttributesCertificateRequest() { return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseCertificateRequest() { + private static Request toRpcResponseCertificateRequest() { return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + private static Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { return getRequest(method, featureType, false, requestId, uriQuery); } - private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + private static Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { return getRequest(method, featureType, true, requestId, uriQuery); } - private Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { + private static Request toProvisionRequest() { + return getRequest(CoAP.Code.POST, PROVISION, true, null, null); + } + + private static Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { var request = new Request(method); var options = new OptionSet(); options.addUriPath(API); From 928962898e266cfabebd949bd2e1b7f106b84769 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 11 Aug 2023 19:23:51 +0300 Subject: [PATCH 46/51] PROD-2339: fix getFeatureType method to handle RPC server-side response over DTLS --- .../transport/coap/CoapTransportResource.java | 11 +- .../coap/CoapTransportResourceTest.java | 352 ++++++++++++++++++ 2 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 7dde25bfd0..263df9e7a2 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -30,6 +30,7 @@ import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; @@ -380,12 +381,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } - private Optional getFeatureType(Request request) { + protected Optional getFeatureType(Request request) { List uriPath = request.getOptions().getUriPath(); try { - if (uriPath.size() >= FEATURE_TYPE_POSITION) { + int size = uriPath.size(); + if (size >= FEATURE_TYPE_POSITION) { + if (size == FEATURE_TYPE_POSITION && StringUtils.isNumeric(uriPath.get(size - 1))) { + return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 2).toUpperCase())); + } return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); - } else if (uriPath.size() >= FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) { + } else if (size == FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) { if (uriPath.contains(DataConstants.PROVISION)) { return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase())); } diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java new file mode 100644 index 0000000000..666f6c95df --- /dev/null +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -0,0 +1,352 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap; + +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.OptionSet; +import org.eclipse.californium.core.coap.Request; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.thingsboard.server.coapserver.CoapServerService; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; +import org.thingsboard.server.transport.coap.client.CoapClientContext; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class CoapTransportResourceTest { + + private static final String V1 = "v1"; + private static final String API = "api"; + private static final String TELEMETRY = "telemetry"; + private static final String ATTRIBUTES = "attributes"; + private static final String RPC = "rpc"; + private static final String CLAIM = "claim"; + private static final String PROVISION = "provision"; + private static final String GET_ATTRIBUTES_URI_QUERY = "clientKeys=attribute1,attribute2&sharedKeys=shared1,shared2"; + + private static final Random RANDOM = new Random(); + + private CoapTransportResource coapTransportResource; + + @BeforeEach + void setUp() { + + var ctxMock = mock(CoapTransportContext.class); + var coapServerServiceMock = mock(CoapServerService.class); + var transportServiceMock = mock(TransportService.class); + var clientContextMock = mock(CoapClientContext.class); + var schedulerComponentMock = mock(SchedulerComponent.class); + + when(ctxMock.getTransportService()).thenReturn(transportServiceMock); + when(ctxMock.getClientContext()).thenReturn(clientContextMock); + when(ctxMock.getSessionReportTimeout()).thenReturn(1L); + when(ctxMock.getScheduler()).thenReturn(schedulerComponentMock); + + coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); + } + + @AfterEach + void tearDown() { + } + + // accessToken based tests + + @Test + void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { + // GIVEN + var request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), TELEMETRY); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toGetAttributesAccessTokenRequest(StringUtils.randomAlphanumeric(20)); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), RPC); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toRpcResponseAccessTokenRequest(StringUtils.randomAlphanumeric(20), RANDOM.nextInt(100)); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), RPC); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), CLAIM); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); + } + + // certificate based tests + + @Test + void givenPostTelemetryCertificateRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.POST, TELEMETRY); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenPostAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.POST, ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenGetAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toGetAttributesCertificateRequest(); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForAttributesUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.GET, ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForRpcUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.GET, RPC); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toRpcResponseCertificateRequest(RANDOM.nextInt(100)); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClientSideRpcCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, RPC); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClaimingCertificateRequest_whenGetFeatureType_thenFeatureTypeClaim() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, CLAIM); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); + } + + // provision request + + @Test + void givenProvisionRequest_whenGetFeatureType_thenFeatureTypeProvision() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, PROVISION); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); + } + + private Request toAccessTokenRequest(CoAP.Code method, String accessToken, String featureType) { + return getAccessTokenRequest(method, accessToken, featureType, null, null); + } + + private Request toGetAttributesAccessTokenRequest(String accessToken) { + return getAccessTokenRequest(CoAP.Code.GET, accessToken, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + } + + private Request toRpcResponseAccessTokenRequest(String accessToken, Integer requestId) { + return getAccessTokenRequest(CoAP.Code.POST, accessToken, CoapTransportResourceTest.RPC, requestId, null); + } + + private Request toCertificateRequest(CoAP.Code method, String featureType) { + return getCertificateRequest(method, featureType, null, null); + } + + private Request toGetAttributesCertificateRequest() { + return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + } + + private Request toRpcResponseCertificateRequest(Integer requestId) { + return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, requestId, null); + } + + private Request getAccessTokenRequest(CoAP.Code method, String accessToken, String featureType, Integer requestId, String uriQuery) { + var request = new Request(method); + var options = new OptionSet(); + options.addUriPath(API); + options.addUriPath(V1); + options.addUriPath(accessToken); + options.addUriPath(featureType); + if (requestId != null) { + options.addUriPath(String.valueOf(requestId)); + } + if (uriQuery != null) { + options.setUriQuery(uriQuery); + } + request.setOptions(options); + return request; + } + + private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + var request = new Request(method); + var options = new OptionSet(); + options.addUriPath(API); + options.addUriPath(V1); + options.addUriPath(featureType); + if (requestId != null) { + options.addUriPath(String.valueOf(requestId)); + } + if (uriQuery != null) { + options.setUriQuery(uriQuery); + } + request.setOptions(options); + return request; + } + + +} From 14ad1df873200ef8d69b82f98bab9cd6f8416d39 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Sat, 12 Aug 2023 09:38:49 +0300 Subject: [PATCH 47/51] refactoring of test base --- .../coap/CoapTransportResourceTest.java | 71 ++++++++----------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java index 666f6c95df..2e5b367e4c 100644 --- a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -18,7 +18,9 @@ package org.thingsboard.server.transport.coap; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.OptionSet; import org.eclipse.californium.core.coap.Request; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.thingsboard.server.coapserver.CoapServerService; @@ -48,10 +50,10 @@ class CoapTransportResourceTest { private static final Random RANDOM = new Random(); - private CoapTransportResource coapTransportResource; + private static CoapTransportResource coapTransportResource; - @BeforeEach - void setUp() { + @BeforeAll + static void setUp() { var ctxMock = mock(CoapTransportContext.class); var coapServerServiceMock = mock(CoapServerService.class); @@ -67,16 +69,12 @@ class CoapTransportResourceTest { coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); } - @AfterEach - void tearDown() { - } - // accessToken based tests @Test void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { // GIVEN - var request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), TELEMETRY); + var request = toAccessTokenRequest(CoAP.Code.POST, TELEMETRY); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -89,7 +87,7 @@ class CoapTransportResourceTest { @Test void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + Request request = toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -102,7 +100,7 @@ class CoapTransportResourceTest { @Test void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toGetAttributesAccessTokenRequest(StringUtils.randomAlphanumeric(20)); + Request request = toGetAttributesAccessTokenRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -114,7 +112,7 @@ class CoapTransportResourceTest { @Test void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + Request request = toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -126,7 +124,7 @@ class CoapTransportResourceTest { @Test void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), RPC); + Request request = toAccessTokenRequest(CoAP.Code.GET, RPC); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -138,7 +136,7 @@ class CoapTransportResourceTest { @Test void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toRpcResponseAccessTokenRequest(StringUtils.randomAlphanumeric(20), RANDOM.nextInt(100)); + Request request = toRpcResponseAccessTokenRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -150,7 +148,7 @@ class CoapTransportResourceTest { @Test void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), RPC); + Request request = toAccessTokenRequest(CoAP.Code.POST, RPC); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -162,7 +160,7 @@ class CoapTransportResourceTest { @Test void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), CLAIM); + Request request = toAccessTokenRequest(CoAP.Code.POST, CLAIM); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -241,7 +239,7 @@ class CoapTransportResourceTest { @Test void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toRpcResponseCertificateRequest(RANDOM.nextInt(100)); + Request request = toRpcResponseCertificateRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -291,16 +289,16 @@ class CoapTransportResourceTest { assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); } - private Request toAccessTokenRequest(CoAP.Code method, String accessToken, String featureType) { - return getAccessTokenRequest(method, accessToken, featureType, null, null); + private Request toAccessTokenRequest(CoAP.Code method, String featureType) { + return getAccessTokenRequest(method, featureType, null, null); } - private Request toGetAttributesAccessTokenRequest(String accessToken) { - return getAccessTokenRequest(CoAP.Code.GET, accessToken, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + private Request toGetAttributesAccessTokenRequest() { + return getAccessTokenRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseAccessTokenRequest(String accessToken, Integer requestId) { - return getAccessTokenRequest(CoAP.Code.POST, accessToken, CoapTransportResourceTest.RPC, requestId, null); + private Request toRpcResponseAccessTokenRequest() { + return getAccessTokenRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } private Request toCertificateRequest(CoAP.Code method, String featureType) { @@ -311,32 +309,26 @@ class CoapTransportResourceTest { return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseCertificateRequest(Integer requestId) { - return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, requestId, null); + private Request toRpcResponseCertificateRequest() { + return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request getAccessTokenRequest(CoAP.Code method, String accessToken, String featureType, Integer requestId, String uriQuery) { - var request = new Request(method); - var options = new OptionSet(); - options.addUriPath(API); - options.addUriPath(V1); - options.addUriPath(accessToken); - options.addUriPath(featureType); - if (requestId != null) { - options.addUriPath(String.valueOf(requestId)); - } - if (uriQuery != null) { - options.setUriQuery(uriQuery); - } - request.setOptions(options); - return request; + private Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + return getRequest(method, featureType, false, requestId, uriQuery); } private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + return getRequest(method, featureType, true, requestId, uriQuery); + } + + private Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { var request = new Request(method); var options = new OptionSet(); options.addUriPath(API); options.addUriPath(V1); + if (!dtls) { + options.addUriPath(StringUtils.randomAlphanumeric(20)); + } options.addUriPath(featureType); if (requestId != null) { options.addUriPath(String.valueOf(requestId)); @@ -348,5 +340,4 @@ class CoapTransportResourceTest { return request; } - } From a90653d6606eee59345943e41d7ec2b27fceb266 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Sat, 12 Aug 2023 09:52:36 +0300 Subject: [PATCH 48/51] refactored to parameterized test --- .../coap/CoapTransportResourceTest.java | 270 +++--------------- 1 file changed, 44 insertions(+), 226 deletions(-) diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java index 2e5b367e4c..c7f33e3694 100644 --- a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -18,11 +18,10 @@ package org.thingsboard.server.transport.coap; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.OptionSet; import org.eclipse.californium.core.coap.Request; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.thingsboard.server.coapserver.CoapServerService; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.msg.session.FeatureType; @@ -31,6 +30,7 @@ import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.transport.coap.client.CoapClientContext; import java.util.Random; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -69,259 +69,77 @@ class CoapTransportResourceTest { coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); } - // accessToken based tests - - @Test - void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { - // GIVEN - var request = toAccessTokenRequest(CoAP.Code.POST, TELEMETRY); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toGetAttributesAccessTokenRequest(); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, RPC); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toRpcResponseAccessTokenRequest(); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, RPC); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, CLAIM); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); - } - - // certificate based tests - - @Test - void givenPostTelemetryCertificateRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.POST, TELEMETRY); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenPostAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.POST, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenGetAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toGetAttributesCertificateRequest(); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForAttributesUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.GET, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForRpcUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.GET, RPC); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toRpcResponseCertificateRequest(); - - // WHEN + @ParameterizedTest + @MethodSource("provideRequestAndFeatureType") + void givenRequest_whenGetFeatureType_thenReturnedExpectedFeatureType(Request request, FeatureType expectedFeatureType) { var featureTypeOptional = coapTransportResource.getFeatureType(request); - // THEN assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + assertEquals(expectedFeatureType, featureTypeOptional.get(), "Feature type is invalid"); } - @Test - void givenClientSideRpcCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, RPC); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + static Stream provideRequestAndFeatureType() { + return Stream.of( + // accessToken based tests + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, TELEMETRY), FeatureType.TELEMETRY), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toGetAttributesAccessTokenRequest(), FeatureType.ATTRIBUTES), + Arguments.of(toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toAccessTokenRequest(CoAP.Code.GET, RPC), FeatureType.RPC), + Arguments.of(toRpcResponseAccessTokenRequest(), FeatureType.RPC), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, RPC), FeatureType.RPC), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, CLAIM), FeatureType.CLAIM), + // certificate based tests + Arguments.of(toCertificateRequest(CoAP.Code.POST, TELEMETRY), FeatureType.TELEMETRY), + Arguments.of(toCertificateRequest(CoAP.Code.POST, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toGetAttributesCertificateRequest(), FeatureType.ATTRIBUTES), + Arguments.of(toCertificateRequest(CoAP.Code.GET, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toCertificateRequest(CoAP.Code.GET, RPC), FeatureType.RPC), + Arguments.of(toRpcResponseCertificateRequest(), FeatureType.RPC), + Arguments.of(toCertificateRequest(CoAP.Code.POST, RPC), FeatureType.RPC), + Arguments.of(toCertificateRequest(CoAP.Code.POST, CLAIM), FeatureType.CLAIM), + // provision request + Arguments.of(toProvisionRequest(), FeatureType.PROVISION) + ); } - @Test - void givenClaimingCertificateRequest_whenGetFeatureType_thenFeatureTypeClaim() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, CLAIM); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); - } - - // provision request - - @Test - void givenProvisionRequest_whenGetFeatureType_thenFeatureTypeProvision() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, PROVISION); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); - } - - private Request toAccessTokenRequest(CoAP.Code method, String featureType) { + private static Request toAccessTokenRequest(CoAP.Code method, String featureType) { return getAccessTokenRequest(method, featureType, null, null); } - private Request toGetAttributesAccessTokenRequest() { + private static Request toGetAttributesAccessTokenRequest() { return getAccessTokenRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseAccessTokenRequest() { + private static Request toRpcResponseAccessTokenRequest() { return getAccessTokenRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request toCertificateRequest(CoAP.Code method, String featureType) { + private static Request toCertificateRequest(CoAP.Code method, String featureType) { return getCertificateRequest(method, featureType, null, null); } - private Request toGetAttributesCertificateRequest() { + private static Request toGetAttributesCertificateRequest() { return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseCertificateRequest() { + private static Request toRpcResponseCertificateRequest() { return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + private static Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { return getRequest(method, featureType, false, requestId, uriQuery); } - private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + private static Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { return getRequest(method, featureType, true, requestId, uriQuery); } - private Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { + private static Request toProvisionRequest() { + return getRequest(CoAP.Code.POST, PROVISION, true, null, null); + } + + private static Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { var request = new Request(method); var options = new OptionSet(); options.addUriPath(API); From 5f39e743ec338e1925c3a9926c4cd2d9a15421c4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 14 Aug 2023 16:35:42 +0300 Subject: [PATCH 49/51] Update form.scss --- ui-ngx/src/form.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 79f141e9d5..8bf8aef2ab 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -380,9 +380,6 @@ gap: 12px; padding-left: 12px; padding-right: 12px; - &.no-padding-right { - padding-right: 0; - } } &-cell { font-weight: 400; From a8f560203694523765a792c985c8f8ca6a70f590 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 15 Aug 2023 00:25:50 +0200 Subject: [PATCH 50/51] SQL partial index added idx_notification_recipient_id_unread for cheap and fast notification count on UI --- .../server/service/install/SqlDatabaseUpgradeService.java | 4 ++++ dao/src/main/resources/sql/schema-entities-idx.sql | 2 ++ 2 files changed, 6 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 34515f827d..2e2b3e96be 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -756,6 +756,10 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_rule_node_type_configuration_version ON rule_node(type, configuration_version);"); } catch (Exception e) { } + try { + conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ';"); + } catch (Exception e) { + } conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005002;"); } diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index 12e0bfddba..675fcd3ec0 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -113,3 +113,5 @@ CREATE INDEX IF NOT EXISTS idx_notification_request_status ON notification_reque CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC); + +CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ'; From 3f2578b6d0416500319be402b3a9e20d7972bfef Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 15 Aug 2023 00:39:08 +0200 Subject: [PATCH 51/51] JpaNotificationDao: JavaDoc added for countUnreadByRecipientId for the reference to the idx_notification_recipient_id_unread --- .../server/dao/sql/notification/JpaNotificationDao.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index 5fa156725d..3d24c6221f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -81,6 +81,9 @@ public class JpaNotificationDao extends JpaAbstractDao