From b7265cb6824f14db7047353d61d2a5cd34447a20 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 25 Jan 2023 12:23:59 +0200 Subject: [PATCH 01/18] Added check if entity exists before processing telemetry update msg --- .../edge/rpc/processor/BaseEdgeProcessor.java | 21 ++++++ .../relation/BaseRelationProcessor.java | 28 -------- .../telemetry/BaseTelemetryProcessor.java | 66 ++++++++++--------- 3 files changed, 56 insertions(+), 59 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index 4b94cee592..36077d3fd5 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -485,4 +485,25 @@ public abstract class BaseEdgeProcessor { } return customerId; } + + protected boolean isEntityExists(TenantId tenantId, EntityId entityId) { + switch (entityId.getEntityType()) { + case DEVICE: + return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null; + case ASSET: + return assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null; + case ENTITY_VIEW: + return entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null; + case CUSTOMER: + return customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null; + case USER: + return userService.findUserById(tenantId, new UserId(entityId.getId())) != null; + case DASHBOARD: + return dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null; + case EDGE: + return edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())) != null; + default: + return false; + } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java index e1c937eaad..36a5decc3f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java @@ -20,16 +20,9 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; @@ -80,25 +73,4 @@ public abstract class BaseRelationProcessor extends BaseEdgeProcessor { return Futures.immediateFailedFuture(e); } } - - private boolean isEntityExists(TenantId tenantId, EntityId entityId) { - switch (entityId.getEntityType()) { - case DEVICE: - return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null; - case ASSET: - return assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null; - case ENTITY_VIEW: - return entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null; - case CUSTOMER: - return customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null; - case USER: - return userService.findUserById(tenantId, new UserId(entityId.getId())) != null; - case DASHBOARD: - return dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null; - case EDGE: - return edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())) != null; - default: - return false; - } - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index 30979187d1..a3d3e3c280 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -90,42 +90,46 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { log.trace("[{}] processTelemetryMsg [{}]", tenantId, entityData); List> result = new ArrayList<>(); EntityId entityId = constructEntityId(entityData.getEntityType(), entityData.getEntityIdMSB(), entityData.getEntityIdLSB()); - if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg() || entityData.hasAttributesUpdatedMsg()) && entityId != null) { - Pair pair = getBaseMsgMetadataAndCustomerId(tenantId, entityId); - TbMsgMetaData metaData = pair.getKey(); - CustomerId customerId = pair.getValue(); - metaData.putValue(DataConstants.MSG_SOURCE_KEY, getMsgSourceKey()); - if (entityData.hasPostAttributesMsg()) { - result.add(processPostAttributes(tenantId, customerId, entityId, entityData.getPostAttributesMsg(), metaData)); - } - if (entityData.hasAttributesUpdatedMsg()) { - metaData.putValue("scope", entityData.getPostAttributeScope()); - result.add(processAttributesUpdate(tenantId, customerId, entityId, entityData.getAttributesUpdatedMsg(), metaData)); - } - if (entityData.hasPostTelemetryMsg()) { - result.add(processPostTelemetry(tenantId, customerId, entityId, entityData.getPostTelemetryMsg(), metaData)); - } - if (EntityType.DEVICE.equals(entityId.getEntityType())) { - DeviceId deviceId = new DeviceId(entityId.getId()); + if (entityId != null && isEntityExists(tenantId, entityId)) { + if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg() || entityData.hasAttributesUpdatedMsg())) { + Pair pair = getBaseMsgMetadataAndCustomerId(tenantId, entityId); + TbMsgMetaData metaData = pair.getKey(); + CustomerId customerId = pair.getValue(); + metaData.putValue(DataConstants.MSG_SOURCE_KEY, getMsgSourceKey()); + if (entityData.hasPostAttributesMsg()) { + result.add(processPostAttributes(tenantId, customerId, entityId, entityData.getPostAttributesMsg(), metaData)); + } + if (entityData.hasAttributesUpdatedMsg()) { + metaData.putValue("scope", entityData.getPostAttributeScope()); + result.add(processAttributesUpdate(tenantId, customerId, entityId, entityData.getAttributesUpdatedMsg(), metaData)); + } + if (entityData.hasPostTelemetryMsg()) { + result.add(processPostTelemetry(tenantId, customerId, entityId, entityData.getPostTelemetryMsg(), metaData)); + } + if (EntityType.DEVICE.equals(entityId.getEntityType())) { + DeviceId deviceId = new DeviceId(entityId.getId()); - long currentTs = System.currentTimeMillis(); + long currentTs = System.currentTimeMillis(); - TransportProtos.DeviceActivityProto deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() - .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) - .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) - .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) - .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) - .setLastActivityTime(currentTs).build(); + TransportProtos.DeviceActivityProto deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) + .setLastActivityTime(currentTs).build(); - log.trace("[{}][{}] device activity time is going to be updated, ts {}", tenantId, deviceId, currentTs); + log.trace("[{}][{}] device activity time is going to be updated, ts {}", tenantId, deviceId, currentTs); - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); - tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(deviceId.getId(), - TransportProtos.ToCoreMsg.newBuilder().setDeviceActivityMsg(deviceActivityMsg).build()), null); + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); + tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(deviceId.getId(), + TransportProtos.ToCoreMsg.newBuilder().setDeviceActivityMsg(deviceActivityMsg).build()), null); + } } - } - if (entityData.hasAttributeDeleteMsg()) { - result.add(processAttributeDeleteMsg(tenantId, entityId, entityData.getAttributeDeleteMsg(), entityData.getEntityType())); + if (entityData.hasAttributeDeleteMsg()) { + result.add(processAttributeDeleteMsg(tenantId, entityId, entityData.getAttributeDeleteMsg(), entityData.getEntityType())); + } + } else { + log.warn("Skipping telemetry update msg because entity doesn't exists on edge, {}", entityData); } return result; } From 096458d268d93c3beddae3b4f8b79ccfd06b5f07 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 25 Jan 2023 15:38:53 +0200 Subject: [PATCH 02/18] Updated edge root rule chain; --- .../rule_chains/edge_root_rule_chain.json | 74 +++++++++++-------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json index f908b1661e..69eae724a2 100644 --- a/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json +++ b/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json @@ -6,7 +6,8 @@ "firstRuleNodeId": null, "root": true, "debugMode": false, - "configuration": null + "configuration": null, + "externalId": null }, "metadata": { "firstNodeIndex": 0, @@ -23,7 +24,8 @@ "configuration": { "persistAlarmRulesState": false, "fetchAlarmRulesStateOnStart": false - } + }, + "externalId": null }, { "additionalInfo": { @@ -35,7 +37,8 @@ "debugMode": false, "configuration": { "defaultTTL": 0 - } + }, + "externalId": null }, { "additionalInfo": { @@ -47,7 +50,8 @@ "debugMode": false, "configuration": { "scope": "CLIENT_SCOPE" - } + }, + "externalId": null }, { "additionalInfo": { @@ -59,7 +63,8 @@ "debugMode": false, "configuration": { "version": 0 - } + }, + "externalId": null }, { "additionalInfo": { @@ -73,7 +78,8 @@ "scriptLang": "TBEL", "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" - } + }, + "externalId": null }, { "additionalInfo": { @@ -87,7 +93,8 @@ "scriptLang": "TBEL", "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" - } + }, + "externalId": null }, { "additionalInfo": { @@ -99,19 +106,34 @@ "debugMode": false, "configuration": { "timeoutInSeconds": 60 - } + }, + "externalId": null }, { "additionalInfo": { - "layoutX": 1129, - "layoutY": 52 + "layoutX": 1126, + "layoutY": 104 }, "type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode", "name": "Push to cloud", "debugMode": false, "configuration": { "scope": "SERVER_SCOPE" - } + }, + "externalId": null + }, + { + "additionalInfo": { + "layoutX": 826, + "layoutY": 601 + }, + "type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode", + "name": "Push to cloud", + "debugMode": false, + "configuration": { + "scope": "SERVER_SCOPE" + }, + "externalId": null } ], "connections": [ @@ -132,24 +154,14 @@ }, { "fromIndex": 3, - "toIndex": 6, - "type": "RPC Request to Device" - }, - { - "fromIndex": 3, - "toIndex": 5, - "type": "Other" + "toIndex": 1, + "type": "Post telemetry" }, { "fromIndex": 3, "toIndex": 2, "type": "Post attributes" }, - { - "fromIndex": 3, - "toIndex": 1, - "type": "Post telemetry" - }, { "fromIndex": 3, "toIndex": 4, @@ -157,23 +169,23 @@ }, { "fromIndex": 3, - "toIndex": 7, - "type": "Attributes Updated" + "toIndex": 5, + "type": "Other" }, { "fromIndex": 3, - "toIndex": 7, - "type": "Attributes Deleted" + "toIndex": 6, + "type": "RPC Request to Device" }, { "fromIndex": 3, - "toIndex": 7, - "type": "Timeseries Deleted" + "toIndex": 8, + "type": "Attributes Deleted" }, { "fromIndex": 3, - "toIndex": 7, - "type": "Timeseries Updated" + "toIndex": 8, + "type": "Attributes Updated" } ], "ruleChainConnections": null From 39ce423ef98259561ba86e73a7d25ad006536087 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 25 Jan 2023 15:40:26 +0200 Subject: [PATCH 03/18] Moved edge root rule chain from tenant dir into edge dir --- .../rule_chains/edge_root_rule_chain.json | 0 .../thingsboard/server/service/install/InstallScripts.java | 5 ++--- 2 files changed, 2 insertions(+), 3 deletions(-) rename application/src/main/data/json/{tenant/edge_management => edge}/rule_chains/edge_root_rule_chain.json (100%) diff --git a/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json similarity index 100% rename from application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json rename to application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 38ff5aacec..893ef5ec20 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -59,6 +59,7 @@ public class InstallScripts { public static final String JSON_DIR = "json"; public static final String SYSTEM_DIR = "system"; public static final String TENANT_DIR = "tenant"; + public static final String EDGE_DIR = "edge"; public static final String DEVICE_PROFILE_DIR = "device_profile"; public static final String DEMO_DIR = "demo"; public static final String RULE_CHAINS_DIR = "rule_chains"; @@ -68,8 +69,6 @@ public class InstallScripts { public static final String MODELS_DIR = "models"; public static final String CREDENTIALS_DIR = "credentials"; - public static final String EDGE_MANAGEMENT = "edge_management"; - public static final String JSON_EXT = ".json"; public static final String XML_EXT = ".xml"; @@ -103,7 +102,7 @@ public class InstallScripts { } private Path getEdgeRuleChainsDir() { - return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, EDGE_MANAGEMENT, RULE_CHAINS_DIR); + return Paths.get(getDataDir(), JSON_DIR, EDGE_DIR, RULE_CHAINS_DIR); } public String getDataDir() { From 7203a0b5c86357721f2229a0f46c747806dc3273 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 25 Jan 2023 15:56:36 +0200 Subject: [PATCH 04/18] Updated edge root rule chain Save Client Attributes rule node config --- .../main/data/json/edge/rule_chains/edge_root_rule_chain.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json index 69eae724a2..ec1341cc71 100644 --- a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json +++ b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json @@ -49,7 +49,8 @@ "name": "Save Client Attributes", "debugMode": false, "configuration": { - "scope": "CLIENT_SCOPE" + "scope": "CLIENT_SCOPE", + "notifyDevice": "false" }, "externalId": null }, From 4228baa33164f11eeaea6b7a33dbcb88c0004411 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 24 Mar 2023 10:51:03 +0200 Subject: [PATCH 05/18] Alarm processor - remove usage of deprecate createOrUpdateAlarm method --- .../service/edge/rpc/fetch/GeneralEdgeEventFetcher.java | 9 +-------- .../edge/rpc/processor/alarm/BaseAlarmProcessor.java | 5 ++--- .../pages/rulechain/rulechains-table-config.resolver.ts | 4 ++-- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java index 327184e6a9..05b973b63c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java @@ -21,7 +21,6 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.edge.EdgeEventService; @@ -33,13 +32,7 @@ public class GeneralEdgeEventFetcher implements EdgeEventFetcher { @Override public PageLink getPageLink(int pageSize) { - return new TimePageLink( - pageSize, - 0, - null, - new SortOrder("createdTime", SortOrder.Direction.ASC), - queueStartTs, - null); + return new TimePageLink(pageSize, 0, null, null, queueStartTs, null); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java index d85750f7d2..c695ca84c6 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java @@ -19,10 +19,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import java.util.UUID; @@ -68,7 +67,7 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { existentAlarm.setAckTs(alarmUpdateMsg.getAckTs()); existentAlarm.setEndTs(alarmUpdateMsg.getEndTs()); existentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); - alarmService.createOrUpdateAlarm(existentAlarm); + alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(existentAlarm)); break; case ALARM_ACK_RPC_MESSAGE: if (existentAlarm != null) { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts index d44fc67764..5360a97054 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts @@ -142,11 +142,11 @@ export class RuleChainsTableConfigResolver implements Resolve('root', 'rulechain.edge-template-root', '60px', + new EntityTableColumn('root', 'rulechain.edge-template-root', '70px', entity => { return checkBoxCell(entity.root); }), - new EntityTableColumn('assignToEdge', 'rulechain.assign-to-edge', '60px', + new EntityTableColumn('assignToEdge', 'rulechain.assign-to-edge', '70px', entity => { return checkBoxCell(this.isAutoAssignToEdgeRuleChain(entity)); }) From fc4be34d55971616535081e9444022d41fb1537f Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 24 Mar 2023 14:22:34 +0200 Subject: [PATCH 06/18] Added edgeAlarmId into AlarmCreateOrUpdateActiveRequest. BaseAlarmProcessor - use alarmId from cloud --- .../processor/alarm/BaseAlarmProcessor.java | 62 ++++++++++--------- .../server/edge/BaseAlarmEdgeTest.java | 6 ++ .../AlarmCreateOrUpdateActiveRequest.java | 8 +++ .../server/dao/sql/alarm/JpaAlarmDao.java | 2 +- 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java index c695ca84c6..84a768a886 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java @@ -42,51 +42,57 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { log.trace("[{}] processAlarmMsg [{}]", tenantId, alarmUpdateMsg); EntityId originatorId = getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(), EntityType.valueOf(alarmUpdateMsg.getOriginatorType())); + AlarmId alarmId = new AlarmId(new UUID(alarmUpdateMsg.getIdMSB(), alarmUpdateMsg.getIdLSB())); if (originatorId == null) { log.warn("Originator not found for the alarm msg {}", alarmUpdateMsg); return Futures.immediateFuture(null); } try { - Alarm existentAlarm = alarmService.findLatestActiveByOriginatorAndType(tenantId, originatorId, alarmUpdateMsg.getType()); switch (alarmUpdateMsg.getMsgType()) { case ENTITY_CREATED_RPC_MESSAGE: case ENTITY_UPDATED_RPC_MESSAGE: - if (existentAlarm == null || existentAlarm.getStatus().isCleared()) { - existentAlarm = new Alarm(); - existentAlarm.setTenantId(tenantId); - existentAlarm.setType(alarmUpdateMsg.getName()); - existentAlarm.setOriginator(originatorId); - existentAlarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity())); - existentAlarm.setStartTs(alarmUpdateMsg.getStartTs()); - existentAlarm.setClearTs(alarmUpdateMsg.getClearTs()); - existentAlarm.setPropagate(alarmUpdateMsg.getPropagate()); + Alarm alarm = alarmService.findAlarmById(tenantId, alarmId); + if (alarm == null) { + alarm = new Alarm(); + alarm.setTenantId(tenantId); + alarm.setType(alarmUpdateMsg.getName()); + alarm.setOriginator(originatorId); + alarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity())); + alarm.setStartTs(alarmUpdateMsg.getStartTs()); } var alarmStatus = AlarmStatus.valueOf(alarmUpdateMsg.getStatus()); - existentAlarm.setCleared(alarmStatus.isCleared()); - existentAlarm.setAcknowledged(alarmStatus.isAck()); - existentAlarm.setAckTs(alarmUpdateMsg.getAckTs()); - existentAlarm.setEndTs(alarmUpdateMsg.getEndTs()); - existentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); - alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(existentAlarm)); - break; + alarm.setClearTs(alarmUpdateMsg.getClearTs()); + alarm.setPropagate(alarmUpdateMsg.getPropagate()); + alarm.setCleared(alarmStatus.isCleared()); + alarm.setAcknowledged(alarmStatus.isAck()); + alarm.setAckTs(alarmUpdateMsg.getAckTs()); + alarm.setEndTs(alarmUpdateMsg.getEndTs()); + alarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); + alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, null, alarmId)); + return Futures.immediateFuture(null); case ALARM_ACK_RPC_MESSAGE: - if (existentAlarm != null) { - alarmService.acknowledgeAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs()); + Alarm alarmToAck = alarmService.findAlarmById(tenantId, alarmId); + if (alarmToAck != null) { + alarmService.acknowledgeAlarm(tenantId, alarmId, alarmUpdateMsg.getAckTs()); } - break; + return Futures.immediateFuture(null); case ALARM_CLEAR_RPC_MESSAGE: - if (existentAlarm != null) { - alarmService.clearAlarm(tenantId, existentAlarm.getId(), - alarmUpdateMsg.getAckTs(), JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); + Alarm alarmToClear = alarmService.findAlarmById(tenantId, alarmId); + if (alarmToClear != null) { + alarmService.clearAlarm(tenantId, alarmId, alarmUpdateMsg.getClearTs(), + JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); } - break; + return Futures.immediateFuture(null); case ENTITY_DELETED_RPC_MESSAGE: - if (existentAlarm != null) { - alarmService.delAlarm(tenantId, existentAlarm.getId()); + Alarm alarmToDelete = alarmService.findAlarmById(tenantId, alarmId); + if (alarmToDelete != null) { + alarmService.delAlarm(tenantId, alarmId); } - break; + return Futures.immediateFuture(null); + case UNRECOGNIZED: + default: + return handleUnsupportedMsgType(alarmUpdateMsg.getMsgType()); } - return Futures.immediateFuture(null); } catch (Exception e) { log.error("[{}] Failed to process alarm update msg [{}]", tenantId, alarmUpdateMsg, e); return Futures.immediateFailedFuture(e); diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java index 1cf2cd44bb..4c73128f3b 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; @@ -33,6 +34,7 @@ import org.thingsboard.server.gen.edge.v1.UplinkMsg; import java.util.List; import java.util.Optional; +import java.util.UUID; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -42,8 +44,11 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest { public void testSendAlarmToCloud() throws Exception { Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge(); + UUID alarmUUID = UUID.randomUUID(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); AlarmUpdateMsg.Builder alarmUpdateMgBuilder = AlarmUpdateMsg.newBuilder(); + alarmUpdateMgBuilder.setIdMSB(alarmUUID.getMostSignificantBits()); + alarmUpdateMgBuilder.setIdLSB(alarmUUID.getLeastSignificantBits()); alarmUpdateMgBuilder.setName("alarm from edge"); alarmUpdateMgBuilder.setStatus(AlarmStatus.ACTIVE_UNACK.name()); alarmUpdateMgBuilder.setSeverity(AlarmSeverity.CRITICAL.name()); @@ -65,6 +70,7 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest { Optional foundAlarm = alarms.stream().filter(alarm -> alarm.getType().equals("alarm from edge")).findAny(); Assert.assertTrue(foundAlarm.isPresent()); AlarmInfo alarmInfo = foundAlarm.get(); + Assert.assertEquals(new AlarmId(alarmUUID), alarmInfo.getId()); Assert.assertEquals(device.getId(), alarmInfo.getOriginator()); Assert.assertEquals(AlarmStatus.ACTIVE_UNACK, alarmInfo.getStatus()); Assert.assertEquals(AlarmSeverity.CRITICAL, alarmInfo.getSeverity()); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java index ecf882e2c9..0f3dd5b636 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import io.swagger.annotations.ApiModelProperty; import lombok.Builder; import lombok.Data; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -61,11 +62,17 @@ public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationReques private UserId userId; + private AlarmId edgeAlarmId; + public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a) { return fromAlarm(a, null); } public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a, UserId userId) { + return fromAlarm(a, userId, null); + } + + public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a, UserId userId, AlarmId edgeAlarmId) { return AlarmCreateOrUpdateActiveRequest.builder() .tenantId(a.getTenantId()) .customerId(a.getCustomerId()) @@ -81,6 +88,7 @@ public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationReques .propagateToTenant(a.isPropagateToTenant()) .propagateRelationTypes(a.getPropagateRelationTypes()).build()) .userId(userId) + .edgeAlarmId(edgeAlarmId) .build(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index f1abd07d95..321208eb8f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -245,7 +245,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A return toAlarmApiResult(alarmRepository.createOrUpdateActiveAlarm( request.getTenantId().getId(), request.getCustomerId() != null ? request.getCustomerId().getId() : CustomerId.NULL_UUID, - UUID.randomUUID(), + request.getEdgeAlarmId() != null ? request.getEdgeAlarmId().getId() : UUID.randomUUID(), System.currentTimeMillis(), request.getOriginator().getId(), request.getOriginator().getEntityType().ordinal(), From 19dcc85262bbb8e76b5f57f3961d9494e992040c Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Tue, 28 Mar 2023 12:30:06 +0300 Subject: [PATCH 07/18] Fixed circular dependency issue - WebSocketService and TbEntityDataSubscriptionService --- .../subscription/DefaultTbEntityDataSubscriptionService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index 9fa8429f78..f001cc4822 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -95,6 +95,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc private final Map> subscriptionsBySessionId = new ConcurrentHashMap<>(); @Autowired + @Lazy private WebSocketService wsService; @Autowired From f7a1a7eabee0176be94f8f2aa87e0c3103e79435 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Tue, 28 Mar 2023 15:05:57 +0300 Subject: [PATCH 08/18] Code review changes --- .../service/edge/rpc/fetch/GeneralEdgeEventFetcher.java | 9 ++++++++- .../DefaultTbEntityDataSubscriptionService.java | 1 - 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java index 05b973b63c..327184e6a9 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.edge.EdgeEventService; @@ -32,7 +33,13 @@ public class GeneralEdgeEventFetcher implements EdgeEventFetcher { @Override public PageLink getPageLink(int pageSize) { - return new TimePageLink(pageSize, 0, null, null, queueStartTs, null); + return new TimePageLink( + pageSize, + 0, + null, + new SortOrder("createdTime", SortOrder.Direction.ASC), + queueStartTs, + null); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index f001cc4822..9fa8429f78 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -95,7 +95,6 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc private final Map> subscriptionsBySessionId = new ConcurrentHashMap<>(); @Autowired - @Lazy private WebSocketService wsService; @Autowired From 5d30fefbcb6b289f0700f1ac28d41c1eded9ccc0 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 30 Mar 2023 17:47:26 +0300 Subject: [PATCH 09/18] Basic implementation of the Starred and Last visited Info --- .../main/data/upgrade/3.4.4/schema_update.sql | 9 +- .../server/controller/BaseController.java | 3 +- .../server/controller/UserController.java | 52 ++++- .../user/DefaultTbUserSettingsService.java | 186 ++++++++++++++++++ .../entitiy/user/TbUserSettingsService.java | 47 +++++ .../server/dao/user/UserSettingsService.java | 8 +- .../settings/AbstractUserDashboardInfo.java | 37 ++++ .../settings/LastVisitedDashboardInfo.java | 37 ++++ .../data/settings/StarredDashboardInfo.java | 36 ++++ .../data/settings/UserDashboardAction.java | 22 +++ .../data/settings/UserDashboardsInfo.java | 46 +++++ .../{security => settings}/UserSettings.java | 12 +- .../settings/UserSettingsCompositeKey.java | 44 +++++ .../server/dao/model/ModelConstants.java | 1 + .../dao/model/sql/UserSettingsEntity.java | 12 +- .../dao/sql/user/JpaUserSettingsDao.java | 11 +- .../dao/sql/user/UserSettingsRepository.java | 3 +- .../dao/user/UserSettingsCaffeineCache.java | 7 +- .../server/dao/user/UserSettingsDao.java | 7 +- .../dao/user/UserSettingsEvictEvent.java | 3 +- .../dao/user/UserSettingsRedisCache.java | 7 +- .../dao/user/UserSettingsServiceImpl.java | 42 ++-- .../main/resources/sql/schema-entities.sql | 6 +- .../dao/service/BaseUserServiceTest.java | 3 +- .../dao/sql/user/JpaUserSettingsDaoTest.java | 9 +- 25 files changed, 584 insertions(+), 66 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardAction.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java rename common/data/src/main/java/org/thingsboard/server/common/data/{security => settings}/UserSettings.java (84%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 6860adc612..5965837898 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -167,11 +167,12 @@ CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notific ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255); CREATE TABLE IF NOT EXISTS user_settings ( - user_id uuid NOT NULL CONSTRAINT user_settings_pkey PRIMARY KEY, - settings varchar(100000), - CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE + user_id uuid NOT NULL, + type VARCHAR(50) NOT NULL, + settings varchar(10000), + CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE, + CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, type) ); - -- ALARM INFO VIEW DROP VIEW IF EXISTS alarm_info CASCADE; diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index a68955d9b4..a6bb7d4d67 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -146,6 +146,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.edge.instructions.EdgeInstallService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.entitiy.user.TbUserSettingsService; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; @@ -202,7 +203,7 @@ public abstract class BaseController { protected UserService userService; @Autowired - protected UserSettingsService userSettingsService; + protected TbUserSettingsService userSettingsService; @Autowired protected DeviceService deviceService; diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index 06963e466f..341ec387e9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -38,12 +38,14 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.UserEmailInfo; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; @@ -55,7 +57,10 @@ import org.thingsboard.server.common.data.query.EntityTypeFilter; import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.common.data.settings.LastVisitedDashboardInfo; +import org.thingsboard.server.common.data.settings.UserDashboardAction; +import org.thingsboard.server.common.data.settings.UserDashboardsInfo; +import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.common.data.security.model.JwtPair; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -76,6 +81,7 @@ import java.util.Map; import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEFAULT_DASHBOARD; import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOARD; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; @@ -438,13 +444,14 @@ public class UserController extends BaseController { } @ApiOperation(value = "Save user settings (saveUserSettings)", - notes = "Save user settings represented in json format for authorized user. " ) + notes = "Save user settings represented in json format for authorized user. ") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PostMapping(value = "/user/settings") public JsonNode saveUserSettings(@RequestBody JsonNode settings) throws ThingsboardException { SecurityUser currentUser = getCurrentUser(); UserSettings userSettings = new UserSettings(); + userSettings.setType(UserSettings.GENERAL); userSettings.setSettings(settings); userSettings.setUserId(currentUser.getId()); return userSettingsService.saveUserSettings(currentUser.getTenantId(), userSettings).getSettings(); @@ -462,27 +469,60 @@ public class UserController extends BaseController { } @ApiOperation(value = "Get user settings (getUserSettings)", - notes = "Fetch the User settings based on authorized user. " ) + notes = "Fetch the User settings based on authorized user. ") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @GetMapping(value = "/user/settings") public JsonNode getUserSettings() throws ThingsboardException { SecurityUser currentUser = getCurrentUser(); UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId()); - return userSettings == null ? JacksonUtil.newObjectNode(): userSettings.getSettings(); + return userSettings == null ? JacksonUtil.newObjectNode() : userSettings.getSettings(); } @ApiOperation(value = "Delete user settings (deleteUserSettings)", notes = "Delete user settings by specifying list of json element xpaths. \n " + - "Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter" ) + "Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/user/settings/{paths}", method = RequestMethod.DELETE) public void deleteUserSettings(@ApiParam(value = PATHS) - @PathVariable(PATHS) String paths) throws ThingsboardException { + @PathVariable(PATHS) String paths) throws ThingsboardException { checkParameter(USER_ID, paths); SecurityUser currentUser = getCurrentUser(); userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), Arrays.asList(paths.split(","))); } + @ApiOperation(value = "Get information about last visited and starred dashboards (getLastVisitedDashboards)", + notes = "Fetch the list of last visited and starred dashboards. Both lists are limited to 10 items.") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @GetMapping(value = "/user/dashboards") + public UserDashboardsInfo getUserDashboardsInfo() throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); + return userSettingsService.findUserDashboardsInfo(currentUser.getTenantId(), currentUser.getId()); + } + + @ApiOperation(value = "Report action of User over the dashboard (reportUserDashboardAction)", + notes = "Enables or Disables user credentials. Useful when you would like to block user account without deleting it. " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/user/dashboards/{dashboardId}/{action}", method = RequestMethod.POST) + @ResponseBody + public UserDashboardsInfo reportUserDashboardAction( + @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @PathVariable(DashboardController.DASHBOARD_ID) String strDashboardId, + @ApiParam(value = "Dashboard action, one of: \"visit\", \"star\" or \"unstar\".") + @PathVariable("action") String strAction) throws ThingsboardException { + checkParameter(DashboardController.DASHBOARD_ID, strDashboardId); + checkParameter("action", strAction); + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); + DashboardInfo dashboard = checkDashboardInfoId(dashboardId, Operation.READ); + UserDashboardAction action; + try { + action = UserDashboardAction.valueOf(strAction.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new ThingsboardException("Action: " + strAction + " is not supported!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + SecurityUser currentUser = getCurrentUser(); + return userSettingsService.reportUserDashboardAction(currentUser.getTenantId(), currentUser.getId(), dashboardId, action); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java new file mode 100644 index 0000000000..add2343e84 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java @@ -0,0 +1,186 @@ +/** + * 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.service.entitiy.user; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.HasTitle; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.settings.AbstractUserDashboardInfo; +import org.thingsboard.server.common.data.settings.LastVisitedDashboardInfo; +import org.thingsboard.server.common.data.settings.StarredDashboardInfo; +import org.thingsboard.server.common.data.settings.UserDashboardAction; +import org.thingsboard.server.common.data.settings.UserDashboardsInfo; +import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.user.UserSettingsService; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +@Service +@TbCoreComponent +@AllArgsConstructor +@Slf4j +public class DefaultTbUserSettingsService implements TbUserSettingsService { + + private static final int MAX_DASHBOARD_INFO_LIST_SIZE = 10; + private static final Predicate EMPTY_TITLE = i -> StringUtils.isNotEmpty(i.getTitle()); + + private final UserSettingsService settingsService; + private final DashboardService dashboardService; + + @Override + public UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings) { + return settingsService.saveUserSettings(tenantId, userSettings); + } + + @Override + public void updateUserSettings(TenantId tenantId, UserId userId, JsonNode settings) { + updateUserSettings(tenantId, userId, UserSettings.GENERAL, settings); + } + + @Override + public void updateUserSettings(TenantId tenantId, UserId userId, String type, JsonNode settings) { + settingsService.updateUserSettings(tenantId, userId, type, settings); + } + + @Override + public UserSettings findUserSettings(TenantId tenantId, UserId userId) { + return findUserSettings(tenantId, userId, UserSettings.GENERAL); + } + + @Override + public UserSettings findUserSettings(TenantId tenantId, UserId userId, String type) { + return settingsService.findUserSettings(tenantId, userId, type); + } + + @Override + public void deleteUserSettings(TenantId tenantId, UserId userId, List jsonPaths) { + deleteUserSettings(tenantId, userId, UserSettings.GENERAL, jsonPaths); + } + + @Override + public void deleteUserSettings(TenantId tenantId, UserId userId, String type, List jsonPaths) { + settingsService.deleteUserSettings(tenantId, userId, type, jsonPaths); + } + + @Override + public UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id) { + UserSettings us = findUserSettings(tenantId, id, UserSettings.STARRED_DASHBOARDS); + if (us == null) { + return UserDashboardsInfo.EMPTY; + } + UserDashboardsInfo stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class); + if (stored == null) { + return UserDashboardsInfo.EMPTY; + } + + if (!stored.getLast().isEmpty()) { + stored.getLast().forEach(i -> setTitleIfEmpty(tenantId, i)); + stored.getLast().removeIf(EMPTY_TITLE); + } + if (!stored.getStarred().isEmpty()) { + Map lastMap = stored.getLast().stream().collect(Collectors.toMap(LastVisitedDashboardInfo::getId, Function.identity())); + stored.getStarred().forEach(i -> { + var last = lastMap.get(i.getId()); + i.setTitle(last != null ? last.getTitle() : null); + }); + stored.getStarred().forEach(i -> setTitleIfEmpty(tenantId, i)); + stored.getStarred().removeIf(EMPTY_TITLE); + } + return stored; + } + + @Override + public UserDashboardsInfo reportUserDashboardAction(TenantId tenantId, UserId id, DashboardId dashboardId, UserDashboardAction action) { + UserSettings us = findUserSettings(tenantId, id, UserSettings.STARRED_DASHBOARDS); + UserDashboardsInfo stored = null; + if (us != null) { + stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class); + } + if (stored == null) { + stored = new UserDashboardsInfo(); + } + + switch (action) { + case STAR: + addToStarred(stored, dashboardId); + break; + case UNSTAR: + removeFromStarred(stored, dashboardId); + break; + case VISIT: + addToVisited(stored, dashboardId); + break; + } + + us = new UserSettings(); + us.setUserId(id); + us.setType(UserSettings.STARRED_DASHBOARDS); + us.setSettings(JacksonUtil.valueToTree(stored)); + saveUserSettings(tenantId, us); + return stored; + } + + private void addToVisited(UserDashboardsInfo stored, DashboardId dashboardId) { + UUID id = dashboardId.getId(); + stored.getStarred().removeIf(d -> id.equals(d.getId())); + } + + private void removeFromStarred(UserDashboardsInfo stored, DashboardId dashboardId) { + UUID id = dashboardId.getId(); + stored.getStarred().removeIf(d -> id.equals(d.getId())); + stored.getLast().stream().filter(d -> id.equals(d.getId())).findFirst().ifPresent(d -> d.setStarred(false)); + } + + private void addToStarred(UserDashboardsInfo stored, DashboardId dashboardId) { + UUID id = dashboardId.getId(); + long ts = System.currentTimeMillis(); + var opt = stored.getStarred().stream().filter(d -> id.equals(d.getId())).findFirst(); + if (opt.isPresent()) { + opt.get().setStarredAt(ts); + } else { + var newInfo = new StarredDashboardInfo(); + newInfo.setId(id); + newInfo.setStarredAt(System.currentTimeMillis()); + stored.getStarred().add(newInfo); + } + stored.getLast().stream().filter(d -> id.equals(d.getId())).forEach(d -> d.setStarred(true)); + //TODO: self-heal if some of the dashboards were deleted. + //TODO: limit by size. + } + + private void setTitleIfEmpty(TenantId tenantId, AbstractUserDashboardInfo i) { + if (StringUtils.isEmpty(i.getTitle())) { + var dashboardInfo = dashboardService.findDashboardInfoById(tenantId, new DashboardId(i.getId())); + i.setTitle(dashboardInfo != null ? dashboardInfo.getTitle() : null); + } + } + + +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java new file mode 100644 index 0000000000..6632e1977c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java @@ -0,0 +1,47 @@ +/** + * 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.service.entitiy.user; + +import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.settings.UserDashboardAction; +import org.thingsboard.server.common.data.settings.UserDashboardsInfo; +import org.thingsboard.server.common.data.settings.UserSettings; + +import java.util.List; + +public interface TbUserSettingsService { + + void updateUserSettings(TenantId tenantId, UserId userId, JsonNode settings); + + void updateUserSettings(TenantId tenantId, UserId userId, String type, JsonNode settings); + + UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings); + + UserSettings findUserSettings(TenantId tenantId, UserId userId); + + UserSettings findUserSettings(TenantId tenantId, UserId userId, String type); + + void deleteUserSettings(TenantId tenantId, UserId userId, List jsonPaths); + + void deleteUserSettings(TenantId tenantId, UserId userId, String type, List jsonPaths); + + UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id); + + UserDashboardsInfo reportUserDashboardAction(TenantId tenantId, UserId id, DashboardId dashboardId, UserDashboardAction action); +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java index 2c6b149607..c2531c5d65 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java @@ -18,18 +18,18 @@ package org.thingsboard.server.dao.user; import com.fasterxml.jackson.databind.JsonNode; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettings; import java.util.List; public interface UserSettingsService { - void updateUserSettings(TenantId tenantId, UserId userId, JsonNode settings); + void updateUserSettings(TenantId tenantId, UserId userId, String type, JsonNode settings); UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings); - UserSettings findUserSettings(TenantId tenantId, UserId userId); + UserSettings findUserSettings(TenantId tenantId, UserId userId, String type); - void deleteUserSettings(TenantId tenantId, UserId userId, List jsonPaths); + void deleteUserSettings(TenantId tenantId, UserId userId, String type, List jsonPaths); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java new file mode 100644 index 0000000000..ce214cb603 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java @@ -0,0 +1,37 @@ +/** + * 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.common.data.settings; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.thingsboard.server.common.data.HasTitle; + +import java.io.Serializable; +import java.util.UUID; + +@ApiModel +@Data +public abstract class AbstractUserDashboardInfo implements HasTitle, Serializable { + + private static final long serialVersionUID = -6461562426034242608L; + + @ApiModelProperty(position = 1, value = "JSON object with Dashboard id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + private UUID id; + @ApiModelProperty(position = 2, value = "Title of the dashboard.") + private String title; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java new file mode 100644 index 0000000000..e49910eb43 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java @@ -0,0 +1,37 @@ +/** + * 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.common.data.settings; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +@EqualsAndHashCode(callSuper = true) +@ApiModel +@Data +public class LastVisitedDashboardInfo extends AbstractUserDashboardInfo implements Serializable { + + private static final long serialVersionUID = -6461562426034242608L; + + @ApiModelProperty(position = 3, value = "Starred flag") + private boolean starred; + @ApiModelProperty(position = 4, value = "Last visit timestamp") + private long lastVisited; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java new file mode 100644 index 0000000000..1e281bd95d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java @@ -0,0 +1,36 @@ +/** + * 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.common.data.settings; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.HasTitle; +import org.thingsboard.server.common.data.id.DashboardId; + +import java.io.Serializable; + +@EqualsAndHashCode(callSuper = true) +@ApiModel +@Data +public class StarredDashboardInfo extends AbstractUserDashboardInfo implements Serializable { + + private static final long serialVersionUID = -7830828696329673361L; + @ApiModelProperty(position = 4, value = "Starred timestamp") + private long starredAt; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardAction.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardAction.java new file mode 100644 index 0000000000..f89e29a45f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardAction.java @@ -0,0 +1,22 @@ +/** + * 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.common.data.settings; + +public enum UserDashboardAction { + + VISIT, STAR, UNSTAR + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java new file mode 100644 index 0000000000..e5423f0d5d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java @@ -0,0 +1,46 @@ +/** + * 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.common.data.settings; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@ApiModel +@Data +@AllArgsConstructor +public class UserDashboardsInfo implements Serializable { + + private static final long serialVersionUID = 2628320657987010348L; + public static final UserDashboardsInfo EMPTY = new UserDashboardsInfo(Collections.emptyList(), Collections.emptyList()); + + @ApiModelProperty(position = 1, value = "List of last visited dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + private List last; + + @ApiModelProperty(position = 2, value = "List of starred dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + private List starred; + + public UserDashboardsInfo() { + this(new ArrayList<>(), new ArrayList<>()); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java similarity index 84% rename from common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java rename to common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java index 5a7fb19ffa..f1318dedfd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.security; +package org.thingsboard.server.common.data.settings; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonProcessingException; @@ -40,10 +40,18 @@ public class UserSettings implements Serializable { private static final long serialVersionUID = 2628320657987010348L; + public static final String GENERAL = "general"; + public static final String STARRED_DASHBOARDS = "starred_dashboards"; + @ApiModelProperty(position = 1, value = "JSON object with User id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private UserId userId; - @ApiModelProperty(position = 2, value = "JSON object with user settings.", dataType = "com.fasterxml.jackson.databind.JsonNode") + @ApiModelProperty(position = 2, value = "Type of the settings.") + @NoXss + @Length(fieldName = "type", max = 50) + private transient String type; + + @ApiModelProperty(position = 3, value = "JSON object with user settings.", dataType = "com.fasterxml.jackson.databind.JsonNode") @NoXss @Length(fieldName = "settings", max = 100000) private transient JsonNode settings; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java new file mode 100644 index 0000000000..72fb432fd4 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java @@ -0,0 +1,44 @@ +/** + * 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.common.data.settings; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.UUID; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class UserSettingsCompositeKey implements Serializable { + + private static final long serialVersionUID = -7883642552545291489L; + + private UUID userId; + private String type; + + public UserSettingsCompositeKey(UserSettings userSettings) { + this.userId = userSettings.getUserId().getId(); + this.type = userSettings.getType(); + } + + @Override + public String toString() { + return userId.toString() + "_" + type; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index a0804e0c71..6197d8f908 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -94,6 +94,7 @@ public class ModelConstants { */ public static final String USER_SETTINGS_COLUMN_FAMILY_NAME = "user_settings"; public static final String USER_SETTINGS_USER_ID_PROPERTY = USER_ID_PROPERTY; + public static final String USER_SETTINGS_TYPE_PROPERTY = "type"; public static final String USER_SETTINGS_SETTINGS = "settings"; /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java index d7abf7ea23..1c38017097 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java @@ -21,7 +21,8 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ToData; import org.thingsboard.server.dao.util.mapping.JsonStringType; @@ -29,6 +30,7 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.IdClass; import javax.persistence.Table; import java.util.UUID; @@ -37,19 +39,24 @@ import java.util.UUID; @TypeDef(name = "json", typeClass = JsonStringType.class) @Entity @Table(name = ModelConstants.USER_SETTINGS_COLUMN_FAMILY_NAME) +@IdClass(UserSettingsCompositeKey.class) public class UserSettingsEntity implements ToData { @Id @Column(name = ModelConstants.USER_SETTINGS_USER_ID_PROPERTY) private UUID userId; + @Id + @Column(name = ModelConstants.USER_SETTINGS_TYPE_PROPERTY) + private String type; @Type(type = "json") @Column(name = ModelConstants.USER_SETTINGS_SETTINGS) private JsonNode settings; public UserSettingsEntity(UserSettings userSettings) { this.userId = userSettings.getUserId().getId(); + this.type = userSettings.getType(); if (userSettings.getSettings() != null) { - this.settings= userSettings.getSettings(); + this.settings = userSettings.getSettings(); } } @@ -57,6 +64,7 @@ public class UserSettingsEntity implements ToData { public UserSettings toData() { UserSettings userSettings = new UserSettings(); userSettings.setUserId(new UserId(userId)); + userSettings.setType(type); if (settings != null) { userSettings.setSettings(settings); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java index 13e8564206..e796faefdb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java @@ -20,7 +20,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.UserSettingsEntity; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; @@ -41,13 +42,13 @@ public class JpaUserSettingsDao extends JpaAbstractDaoListeningExecutorService i } @Override - public UserSettings findById(TenantId tenantId, UserId userId) { - return DaoUtil.getData(userSettingsRepository.findById(userId.getId())); + public UserSettings findById(TenantId tenantId, UserId userId, String type) { + return DaoUtil.getData(userSettingsRepository.findById(new UserSettingsCompositeKey(userId.getId(), type))); } @Override - public void removeById(TenantId tenantId, UserId userId) { - userSettingsRepository.deleteById(userId.getId()); + public void removeById(TenantId tenantId, UserId userId, String type) { + userSettingsRepository.deleteById(new UserSettingsCompositeKey(userId.getId(), type)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java index 6b3e28abc9..f5f4603f14 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java @@ -16,10 +16,11 @@ package org.thingsboard.server.dao.sql.user; import org.springframework.data.jpa.repository.JpaRepository; +import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; import org.thingsboard.server.dao.model.sql.UserSettingsEntity; import java.util.UUID; -public interface UserSettingsRepository extends JpaRepository { +public interface UserSettingsRepository extends JpaRepository { } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java index 3154e5d8c0..f8c55c5d94 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java @@ -20,14 +20,13 @@ import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CaffeineTbTransactionalCache; import org.thingsboard.server.common.data.CacheConstants; -import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserSettings; -import org.thingsboard.server.dao.asset.AssetCacheKey; +import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) @Service("UserSettingsCache") -public class UserSettingsCaffeineCache extends CaffeineTbTransactionalCache { +public class UserSettingsCaffeineCache extends CaffeineTbTransactionalCache { public UserSettingsCaffeineCache(CacheManager cacheManager) { super(cacheManager, CacheConstants.USER_SETTINGS_CACHE); diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java index 16a7675a64..8ebd6a534a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java @@ -17,14 +17,15 @@ package org.thingsboard.server.dao.user; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; public interface UserSettingsDao { UserSettings save(TenantId tenantId, UserSettings userSettings); - UserSettings findById(TenantId tenantId, UserId userId); + UserSettings findById(TenantId tenantId, UserSettingsCompositeKey key); - void removeById(TenantId tenantId, UserId userId); + void removeById(TenantId tenantId, UserSettingsCompositeKey key); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java index e0d2de7ece..bfe0142cc6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java @@ -17,8 +17,9 @@ package org.thingsboard.server.dao.user; import lombok.Data; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; @Data public class UserSettingsEvictEvent { - private final UserId userId; + private final UserSettingsCompositeKey key; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java index a510e6c864..de321f3eb9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java @@ -23,14 +23,13 @@ import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.cache.TbFSTRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; -import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserSettings; -import org.thingsboard.server.dao.asset.AssetCacheKey; +import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("UserSettingsCache") -public class UserSettingsRedisCache extends RedisTbTransactionalCache { +public class UserSettingsRedisCache extends RedisTbTransactionalCache { public UserSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { super(CacheConstants.USER_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java index d94e060383..88321faf66 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java @@ -15,38 +15,35 @@ */ package org.thingsboard.server.dao.user; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.github.fge.jackson.NodeType; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; -import com.jayway.jsonpath.PathNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; import org.thingsboard.server.dao.entity.AbstractCachedService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.ConstraintValidator; import java.util.ArrayList; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import static org.thingsboard.server.dao.service.Validator.validateId; @Service("UserSettingsDaoService") @Slf4j @RequiredArgsConstructor -public class UserSettingsServiceImpl extends AbstractCachedService implements UserSettingsService { +public class UserSettingsServiceImpl extends AbstractCachedService implements UserSettingsService { public static final String INCORRECT_USER_ID = "Incorrect userId "; private final UserSettingsDao userSettingsDao; @@ -58,33 +55,37 @@ public class UserSettingsServiceImpl extends AbstractCachedService userSettingsDao.findById(tenantId, userId), true); + var key = new UserSettingsCompositeKey(userId.getId(), type); + return cache.getAndPutInTransaction(key, + () -> userSettingsDao.findById(tenantId, key), true); } @Override - public void deleteUserSettings(TenantId tenantId, UserId userId, List jsonPaths) { + public void deleteUserSettings(TenantId tenantId, UserId userId, String type, List jsonPaths) { log.trace("Executing deleteUserSettings for user [{}]", userId); validateId(userId, INCORRECT_USER_ID + userId); - UserSettings userSettings = userSettingsDao.findById(tenantId, userId); + var key = new UserSettingsCompositeKey(userId.getId(), type); + UserSettings userSettings = userSettingsDao.findById(tenantId, key); if (userSettings == null) { return; } @@ -95,7 +96,7 @@ public class UserSettingsServiceImpl extends AbstractCachedService keys = new ArrayList<>(); - keys.add(event.getUserId()); - cache.evict(keys); + cache.evict(event.getKey()); } private void validateJsonKeys(JsonNode userSettings) { diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 4e60c7105e..4197dc2b07 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -850,9 +850,11 @@ CREATE TABLE IF NOT EXISTS notification ( ) PARTITION BY RANGE (created_time); CREATE TABLE IF NOT EXISTS user_settings ( - user_id uuid NOT NULL CONSTRAINT user_settings_pkey PRIMARY KEY, + user_id uuid NOT NULL, + type VARCHAR(50) NOT NULL, settings varchar(10000), - CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE + CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE, + CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, type) ); DROP VIEW IF EXISTS alarm_info CASCADE; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java index 14917f6807..c7ff515fb5 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java @@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -32,7 +31,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.user.UserService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java index 4260221a73..b5b578947d 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.service.AbstractServiceTest; import org.thingsboard.server.dao.user.UserDao; @@ -66,17 +66,18 @@ public class JpaUserSettingsDaoTest extends AbstractJpaDaoTest { public void testFindSettingsByUserId() { UserSettings userSettings = createUserSettings(user.getId()); - UserSettings retrievedUserSettings = userSettingsDao.findById(SYSTEM_TENANT_ID, user.getId()); + UserSettings retrievedUserSettings = userSettingsDao.findById(SYSTEM_TENANT_ID, user.getId(), UserSettings.GENERAL); assertEquals(retrievedUserSettings.getSettings(), userSettings.getSettings()); - userSettingsDao.removeById(SYSTEM_TENANT_ID, user.getId()); + userSettingsDao.removeById(SYSTEM_TENANT_ID, user.getId(), UserSettings.GENERAL); - UserSettings retrievedUserSettings2 = userSettingsDao.findById(SYSTEM_TENANT_ID, user.getId()); + UserSettings retrievedUserSettings2 = userSettingsDao.findById(SYSTEM_TENANT_ID, user.getId(), UserSettings.GENERAL); assertNull(retrievedUserSettings2); } private UserSettings createUserSettings(UserId userId) { UserSettings userSettings = new UserSettings(); + userSettings.setType(UserSettings.GENERAL); userSettings.setSettings(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10))); userSettings.setUserId(userId); return userSettingsDao.save(SYSTEM_TENANT_ID, userSettings); From 1430930bd2cd6b18b40aa54680c2997fabc41f7c Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 30 Mar 2023 18:51:21 +0300 Subject: [PATCH 10/18] Integration tests --- .../server/controller/UserController.java | 2 +- .../user/DefaultTbUserSettingsService.java | 73 ++++++--- .../controller/BaseUserControllerTest.java | 139 ++++++++++++++++-- .../dao/sql/user/JpaUserSettingsDao.java | 9 +- .../dao/sql/user/JpaUserSettingsDaoTest.java | 7 +- 5 files changed, 188 insertions(+), 42 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index 341ec387e9..4536d77a62 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -504,7 +504,7 @@ public class UserController extends BaseController { @ApiOperation(value = "Report action of User over the dashboard (reportUserDashboardAction)", notes = "Enables or Disables user credentials. Useful when you would like to block user account without deleting it. " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/user/dashboards/{dashboardId}/{action}", method = RequestMethod.POST) + @RequestMapping(value = "/user/dashboards/{dashboardId}/{action}", method = RequestMethod.GET) @ResponseBody public UserDashboardsInfo reportUserDashboardAction( @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java index add2343e84..20a20a4bfc 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.entitiy.user; import com.fasterxml.jackson.databind.JsonNode; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.HasTitle; @@ -35,8 +36,11 @@ import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.user.UserSettingsService; import org.thingsboard.server.queue.util.TbCoreComponent; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.function.Function; import java.util.function.Predicate; @@ -49,7 +53,7 @@ import java.util.stream.Collectors; public class DefaultTbUserSettingsService implements TbUserSettingsService { private static final int MAX_DASHBOARD_INFO_LIST_SIZE = 10; - private static final Predicate EMPTY_TITLE = i -> StringUtils.isNotEmpty(i.getTitle()); + private static final Predicate EMPTY_TITLE = i -> StringUtils.isEmpty(i.getTitle()); private final UserSettingsService settingsService; private final DashboardService dashboardService; @@ -96,24 +100,7 @@ public class DefaultTbUserSettingsService implements TbUserSettingsService { return UserDashboardsInfo.EMPTY; } UserDashboardsInfo stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class); - if (stored == null) { - return UserDashboardsInfo.EMPTY; - } - - if (!stored.getLast().isEmpty()) { - stored.getLast().forEach(i -> setTitleIfEmpty(tenantId, i)); - stored.getLast().removeIf(EMPTY_TITLE); - } - if (!stored.getStarred().isEmpty()) { - Map lastMap = stored.getLast().stream().collect(Collectors.toMap(LastVisitedDashboardInfo::getId, Function.identity())); - stored.getStarred().forEach(i -> { - var last = lastMap.get(i.getId()); - i.setTitle(last != null ? last.getTitle() : null); - }); - stored.getStarred().forEach(i -> setTitleIfEmpty(tenantId, i)); - stored.getStarred().removeIf(EMPTY_TITLE); - } - return stored; + return getUserDashboardsInfo(tenantId, stored); } @Override @@ -144,12 +131,26 @@ public class DefaultTbUserSettingsService implements TbUserSettingsService { us.setType(UserSettings.STARRED_DASHBOARDS); us.setSettings(JacksonUtil.valueToTree(stored)); saveUserSettings(tenantId, us); - return stored; + return getUserDashboardsInfo(tenantId, stored); } private void addToVisited(UserDashboardsInfo stored, DashboardId dashboardId) { UUID id = dashboardId.getId(); - stored.getStarred().removeIf(d -> id.equals(d.getId())); + long ts = System.currentTimeMillis(); + var opt = stored.getLast().stream().filter(d -> id.equals(d.getId())).findFirst(); + if (opt.isPresent()) { + opt.get().setLastVisited(ts); + } else { + var newInfo = new LastVisitedDashboardInfo(); + newInfo.setId(id); + newInfo.setStarred(stored.getStarred().stream().anyMatch(d -> id.equals(d.getId()))); + newInfo.setLastVisited(System.currentTimeMillis()); + stored.getLast().add(newInfo); + } + stored.getLast().sort(Comparator.comparing(LastVisitedDashboardInfo::getLastVisited).reversed()); + if (stored.getLast().size() > MAX_DASHBOARD_INFO_LIST_SIZE) { + stored.setLast(stored.getLast().stream().limit(MAX_DASHBOARD_INFO_LIST_SIZE).collect(Collectors.toList())); + } } private void removeFromStarred(UserDashboardsInfo stored, DashboardId dashboardId) { @@ -170,9 +171,13 @@ public class DefaultTbUserSettingsService implements TbUserSettingsService { newInfo.setStarredAt(System.currentTimeMillis()); stored.getStarred().add(newInfo); } - stored.getLast().stream().filter(d -> id.equals(d.getId())).forEach(d -> d.setStarred(true)); - //TODO: self-heal if some of the dashboards were deleted. - //TODO: limit by size. + stored.getStarred().sort(Comparator.comparing(StarredDashboardInfo::getStarredAt).reversed()); + if (stored.getStarred().size() > MAX_DASHBOARD_INFO_LIST_SIZE) { + stored.setStarred(stored.getStarred().stream().limit(MAX_DASHBOARD_INFO_LIST_SIZE).collect(Collectors.toList())); + } + Set starredMap = + stored.getStarred().stream().map(AbstractUserDashboardInfo::getId).collect(Collectors.toSet()); + stored.getLast().forEach(d -> d.setStarred(starredMap.contains(d.getId()))); } private void setTitleIfEmpty(TenantId tenantId, AbstractUserDashboardInfo i) { @@ -182,5 +187,25 @@ public class DefaultTbUserSettingsService implements TbUserSettingsService { } } + private UserDashboardsInfo getUserDashboardsInfo(TenantId tenantId, UserDashboardsInfo stored) { + if (stored == null) { + return UserDashboardsInfo.EMPTY; + } + + if (!stored.getLast().isEmpty()) { + stored.getLast().forEach(i -> setTitleIfEmpty(tenantId, i)); + stored.getLast().removeIf(EMPTY_TITLE); + } + if (!stored.getStarred().isEmpty()) { + Map lastMap = stored.getLast().stream().collect(Collectors.toMap(LastVisitedDashboardInfo::getId, Function.identity())); + stored.getStarred().forEach(i -> { + var last = lastMap.get(i.getId()); + i.setTitle(last != null ? last.getTitle() : null); + }); + stored.getStarred().forEach(i -> setTitleIfEmpty(tenantId, i)); + stored.getStarred().removeIf(EMPTY_TITLE); + } + return stored; + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index 7149f52630..84fab2d971 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -31,6 +31,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.servlet.ResultActions; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; @@ -42,6 +43,8 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.settings.StarredDashboardInfo; +import org.thingsboard.server.common.data.settings.UserDashboardsInfo; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.user.UserDao; @@ -356,7 +359,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { String userIdStr = savedUser.getId().getId().toString(); doGet("/api/user/" + userIdStr) .andExpect(status().isNotFound()) - .andExpect(statusReason(containsString( msgErrorNoFound("User",userIdStr)))); + .andExpect(statusReason(containsString(msgErrorNoFound("User", userIdStr)))); } @Test @@ -574,9 +577,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { String email1 = "testEmail1"; String email2 = "testEmail2"; List customerUsersEmail1 = new ArrayList<>(); - List customerUsersEmail2= new ArrayList<>(); + List customerUsersEmail2 = new ArrayList<>(); for (int i = 0; i < 45; i++) { - User customerUser = createCustomerUser( customerId); + User customerUser = createCustomerUser(customerId); customerUser.setEmail(email1 + StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)) + "@thingsboard.org"); customerUsersEmail1.add(doPost("/api/user", customerUser, User.class)); @@ -685,7 +688,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { JsonNode retrievedSettings = doGet("/api/user/settings", JsonNode.class); Assert.assertEquals(retrievedSettings, userSettings); - } + } @Test public void testShouldNotSaveJsonWithRestrictedSymbols() throws Exception { @@ -860,7 +863,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { List expectedUserInfos = customerUsersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(), customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(), - customerUser.getLastName() == null ? "" : customerUser.getLastName())) + customerUser.getLastName() == null ? "" : customerUser.getLastName())) .sorted(userDataIdComparator).collect(Collectors.toList()); usersInfo.sort(userDataIdComparator); @@ -912,8 +915,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { List usersInfo = getUsersInfo(pageLink); List expectedUserInfos = usersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(), - customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(), - customerUser.getLastName() == null ? "" : customerUser.getLastName())) + customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(), + customerUser.getLastName() == null ? "" : customerUser.getLastName())) .sorted(userDataIdComparator).collect(Collectors.toList()); usersInfo.sort(userDataIdComparator); @@ -922,7 +925,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { // find user by full last name pageLink = new PageLink(10, 0, searchText + "3"); usersInfo = getUsersInfo(pageLink); - Assert.assertEquals(2, usersInfo.size()); + Assert.assertEquals(2, usersInfo.size()); //clear users doDelete("/api/customer/" + customerId.getId().toString()) @@ -941,6 +944,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { private static User createCustomerUser(CustomerId customerId) { return createCustomerUser(null, null, customerId); } + private static User createCustomerUser(String firstName, String lastName, CustomerId customerId) { String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); return createCustomerUser(firstName, lastName, "testMail" + suffix + "@thingsboard.org", customerId); @@ -959,6 +963,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { private User createTenantAdminUser() { return createTenantAdminUser(null, null); } + private User createTenantAdminUser(String firstName, String lastName) { String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); @@ -975,7 +980,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { List loadedCustomerUsers = new ArrayList<>(); PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/users/info?", new TypeReference<>() {}, pageLink); + pageData = doGetTypedWithPageLink("/api/users/info?", new TypeReference<>() { + }, pageLink); loadedCustomerUsers.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -984,4 +990,119 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { return loadedCustomerUsers; } + @Test + public void testEmptyDashboardSettings() throws Exception { + loginCustomerUser(); + + UserDashboardsInfo retrievedSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class); + Assert.assertNotNull(retrievedSettings); + Assert.assertNotNull(retrievedSettings.getLast()); + Assert.assertTrue(retrievedSettings.getLast().isEmpty()); + Assert.assertNotNull(retrievedSettings.getStarred()); + Assert.assertTrue(retrievedSettings.getStarred().isEmpty()); + } + + @Test + public void testDashboardSettingsFlow() throws Exception { + loginTenantAdmin(); + + Dashboard dashboard1 = new Dashboard(); + dashboard1.setTitle("My dashboard 1"); + Dashboard savedDashboard1 = doPost("/api/dashboard", dashboard1, Dashboard.class); + Dashboard dashboard2 = new Dashboard(); + dashboard2.setTitle("My dashboard 2"); + Dashboard savedDashboard2 = doPost("/api/dashboard", dashboard2, Dashboard.class); + + UserDashboardsInfo retrievedSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class); + Assert.assertNotNull(retrievedSettings); + Assert.assertNotNull(retrievedSettings.getLast()); + Assert.assertTrue(retrievedSettings.getLast().isEmpty()); + Assert.assertNotNull(retrievedSettings.getStarred()); + Assert.assertTrue(retrievedSettings.getStarred().isEmpty()); + + UserDashboardsInfo newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/visit", UserDashboardsInfo.class); + Assert.assertNotNull(newSettings); + Assert.assertNotNull(newSettings.getLast()); + Assert.assertEquals(1, newSettings.getLast().size()); + var lastVisited = newSettings.getLast().get(0); + Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle()); + Assert.assertNotNull(retrievedSettings.getStarred()); + Assert.assertTrue(retrievedSettings.getStarred().isEmpty()); + + newSettings = doGet("/api/user/dashboards/" + savedDashboard2.getId().getId() + "/visit", UserDashboardsInfo.class); + Assert.assertNotNull(newSettings); + Assert.assertNotNull(newSettings.getLast()); + Assert.assertEquals(2, newSettings.getLast().size()); + lastVisited = newSettings.getLast().get(0); + Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle()); + Assert.assertNotNull(retrievedSettings.getStarred()); + Assert.assertTrue(retrievedSettings.getStarred().isEmpty()); + + newSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class); + Assert.assertNotNull(newSettings); + Assert.assertNotNull(newSettings.getLast()); + Assert.assertEquals(2, newSettings.getLast().size()); + lastVisited = newSettings.getLast().get(0); + Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle()); + Assert.assertNotNull(retrievedSettings.getStarred()); + Assert.assertTrue(retrievedSettings.getStarred().isEmpty()); + + newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/star", UserDashboardsInfo.class); + Assert.assertNotNull(newSettings); + Assert.assertNotNull(newSettings.getLast()); + Assert.assertEquals(2, newSettings.getLast().size()); + lastVisited = newSettings.getLast().get(0); + Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle()); + Assert.assertFalse(lastVisited.isStarred()); + lastVisited = newSettings.getLast().get(1); + Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle()); + Assert.assertTrue(lastVisited.isStarred()); + Assert.assertNotNull(retrievedSettings.getStarred()); + Assert.assertEquals(1, newSettings.getStarred().size()); + StarredDashboardInfo starred = newSettings.getStarred().get(0); + Assert.assertEquals(savedDashboard1.getId().getId(), starred.getId()); + Assert.assertEquals(savedDashboard1.getTitle(), starred.getTitle()); + + newSettings = doGet("/api/user/dashboards/" + savedDashboard2.getId().getId() + "/star", UserDashboardsInfo.class); + Assert.assertNotNull(newSettings); + Assert.assertNotNull(newSettings.getLast()); + Assert.assertEquals(2, newSettings.getLast().size()); + lastVisited = newSettings.getLast().get(0); + Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle()); + Assert.assertTrue(lastVisited.isStarred()); + lastVisited = newSettings.getLast().get(1); + Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle()); + Assert.assertTrue(lastVisited.isStarred()); + Assert.assertNotNull(retrievedSettings.getStarred()); + Assert.assertEquals(2, newSettings.getStarred().size()); + starred = newSettings.getStarred().get(0); + Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId()); + Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle()); + + newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/unstar", UserDashboardsInfo.class); + Assert.assertNotNull(newSettings); + Assert.assertNotNull(newSettings.getLast()); + Assert.assertEquals(2, newSettings.getLast().size()); + lastVisited = newSettings.getLast().get(0); + Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle()); + Assert.assertTrue(lastVisited.isStarred()); + lastVisited = newSettings.getLast().get(1); + Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle()); + Assert.assertFalse(lastVisited.isStarred()); + Assert.assertNotNull(retrievedSettings.getStarred()); + Assert.assertEquals(1, newSettings.getStarred().size()); + starred = newSettings.getStarred().get(0); + Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId()); + Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle()); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java index e796faefdb..c0b270ccbb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java @@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; import org.thingsboard.server.dao.DaoUtil; @@ -42,13 +41,13 @@ public class JpaUserSettingsDao extends JpaAbstractDaoListeningExecutorService i } @Override - public UserSettings findById(TenantId tenantId, UserId userId, String type) { - return DaoUtil.getData(userSettingsRepository.findById(new UserSettingsCompositeKey(userId.getId(), type))); + public UserSettings findById(TenantId tenantId, UserSettingsCompositeKey id) { + return DaoUtil.getData(userSettingsRepository.findById(id)); } @Override - public void removeById(TenantId tenantId, UserId userId, String type) { - userSettingsRepository.deleteById(new UserSettingsCompositeKey(userId.getId(), type)); + public void removeById(TenantId tenantId, UserSettingsCompositeKey id) { + userSettingsRepository.deleteById(id); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java index b5b578947d..d161af6b21 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.service.AbstractServiceTest; import org.thingsboard.server.dao.user.UserDao; @@ -66,12 +67,12 @@ public class JpaUserSettingsDaoTest extends AbstractJpaDaoTest { public void testFindSettingsByUserId() { UserSettings userSettings = createUserSettings(user.getId()); - UserSettings retrievedUserSettings = userSettingsDao.findById(SYSTEM_TENANT_ID, user.getId(), UserSettings.GENERAL); + UserSettings retrievedUserSettings = userSettingsDao.findById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettings.GENERAL)); assertEquals(retrievedUserSettings.getSettings(), userSettings.getSettings()); - userSettingsDao.removeById(SYSTEM_TENANT_ID, user.getId(), UserSettings.GENERAL); + userSettingsDao.removeById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettings.GENERAL)); - UserSettings retrievedUserSettings2 = userSettingsDao.findById(SYSTEM_TENANT_ID, user.getId(), UserSettings.GENERAL); + UserSettings retrievedUserSettings2 = userSettingsDao.findById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettings.GENERAL)); assertNull(retrievedUserSettings2); } From bacda7e3f6752904de9b6af7a7cb30ccfd28cec4 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 31 Mar 2023 12:32:19 +0300 Subject: [PATCH 11/18] Base Edge Processor - Added check for tenant entity --- .../server/service/edge/rpc/processor/BaseEdgeProcessor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index 4c8cf585a5..ac7c791338 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -488,6 +488,8 @@ public abstract class BaseEdgeProcessor { protected boolean isEntityExists(TenantId tenantId, EntityId entityId) { switch (entityId.getEntityType()) { + case TENANT: + return tenantService.findTenantById(tenantId) != null; case DEVICE: return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null; case ASSET: From 95e9f5b715a264c14aa13a3ccd4878243ebfa184 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 31 Mar 2023 17:17:12 +0300 Subject: [PATCH 12/18] Improvements --- .../server/controller/BaseController.java | 10 +++ .../server/controller/UserController.java | 67 +++++++++++--- .../user/DefaultTbUserSettingsService.java | 89 +++++++++---------- .../entitiy/user/TbUserSettingsService.java | 13 +-- .../controller/BaseUserControllerTest.java | 24 +++++ .../dao/dashboard/DashboardService.java | 2 + .../server/dao/user/UserSettingsService.java | 7 +- .../common/data/settings/UserSettings.java | 5 +- .../settings/UserSettingsCompositeKey.java | 2 +- .../data/settings/UserSettingsType.java | 34 +++++++ .../dao/model/sql/UserSettingsEntity.java | 5 +- .../dao/user/UserSettingsServiceImpl.java | 13 +-- .../dao/sql/user/JpaUserSettingsDaoTest.java | 9 +- 13 files changed, 191 insertions(+), 89 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index a6bb7d4d67..34f065d500 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -103,6 +103,7 @@ import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.settings.UserDashboardAction; import org.thingsboard.server.common.data.util.ThrowingBiFunction; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; @@ -169,6 +170,7 @@ import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Collectors; import static org.thingsboard.server.common.data.StringUtils.isNotEmpty; @@ -446,6 +448,14 @@ public abstract class BaseController { } } + protected T checkEnumParameter(String name, String param, Function valueOf) throws ThingsboardException { + try { + return valueOf.apply(param.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new ThingsboardException(name + " \"" + param + "\" is not supported!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + } + UUID toUUID(String id) throws ThingsboardException { try { return UUID.fromString(id); diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index 4536d77a62..311622db5d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -63,6 +63,7 @@ import org.thingsboard.server.common.data.settings.UserDashboardsInfo; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.common.data.security.model.JwtPair; +import org.thingsboard.server.common.data.settings.UserSettingsType; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.user.TbUserService; import org.thingsboard.server.service.query.EntityQueryService; @@ -451,7 +452,7 @@ public class UserController extends BaseController { SecurityUser currentUser = getCurrentUser(); UserSettings userSettings = new UserSettings(); - userSettings.setType(UserSettings.GENERAL); + userSettings.setType(UserSettingsType.GENERAL); userSettings.setSettings(settings); userSettings.setUserId(currentUser.getId()); return userSettingsService.saveUserSettings(currentUser.getTenantId(), userSettings).getSettings(); @@ -465,7 +466,7 @@ public class UserController extends BaseController { @PutMapping(value = "/user/settings") public void putUserSettings(@RequestBody JsonNode settings) throws ThingsboardException { SecurityUser currentUser = getCurrentUser(); - userSettingsService.updateUserSettings(currentUser.getTenantId(), currentUser.getId(), settings); + userSettingsService.updateUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL, settings); } @ApiOperation(value = "Get user settings (getUserSettings)", @@ -475,7 +476,7 @@ public class UserController extends BaseController { public JsonNode getUserSettings() throws ThingsboardException { SecurityUser currentUser = getCurrentUser(); - UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId()); + UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL); return userSettings == null ? JacksonUtil.newObjectNode() : userSettings.getSettings(); } @@ -489,7 +490,50 @@ public class UserController extends BaseController { checkParameter(USER_ID, paths); SecurityUser currentUser = getCurrentUser(); - userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), Arrays.asList(paths.split(","))); + userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL, Arrays.asList(paths.split(","))); + } + + @ApiOperation(value = "Update user settings (saveUserSettings)", + notes = "Update user settings for authorized user. Only specified json elements will be updated." + + "Example: you have such settings: {A:5, B:{C:10, D:20}}. Updating it with {B:{C:10, D:30}} will result in" + + "{A:5, B:{C:10, D:30}}. The same could be achieved by putting {B.D:30}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @PutMapping(value = "/user/settings/{type}") + public void putUserSettings(@ApiParam(value = "Settings type, one of: \"visit\", \"star\" or \"unstar\".") + @PathVariable("type") String strType, @RequestBody JsonNode settings) throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); + UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); + checkNotReserved(strType, type); + userSettingsService.updateUserSettings(currentUser.getTenantId(), currentUser.getId(), type, settings); + } + + @ApiOperation(value = "Get user settings (getUserSettings)", + notes = "Fetch the User settings based on authorized user. ") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @GetMapping(value = "/user/settings/{type}") + public JsonNode getUserSettings(@ApiParam(value = "Settings type, one of: \"visit\", \"star\" or \"unstar\".") + @PathVariable("type") String strType) throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); + UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); + checkNotReserved(strType, type); + UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId(), type); + return userSettings == null ? JacksonUtil.newObjectNode() : userSettings.getSettings(); + } + + @ApiOperation(value = "Delete user settings (deleteUserSettings)", + notes = "Delete user settings by specifying list of json element xpaths. \n " + + "Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/user/settings/{type}/{paths}", method = RequestMethod.DELETE) + public void deleteUserSettings(@ApiParam(value = PATHS) + @PathVariable(PATHS) String paths, + @ApiParam(value = "Settings type, one of: \"visit\", \"star\" or \"unstar\".") + @PathVariable("type") String strType) throws ThingsboardException { + checkParameter(USER_ID, paths); + UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); + checkNotReserved(strType, type); + SecurityUser currentUser = getCurrentUser(); + userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), type, Arrays.asList(paths.split(","))); } @ApiOperation(value = "Get information about last visited and starred dashboards (getLastVisitedDashboards)", @@ -513,16 +557,17 @@ public class UserController extends BaseController { @PathVariable("action") String strAction) throws ThingsboardException { checkParameter(DashboardController.DASHBOARD_ID, strDashboardId); checkParameter("action", strAction); + UserDashboardAction action = checkEnumParameter("Action", strAction, UserDashboardAction::valueOf); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - DashboardInfo dashboard = checkDashboardInfoId(dashboardId, Operation.READ); - UserDashboardAction action; - try { - action = UserDashboardAction.valueOf(strAction.toUpperCase()); - } catch (IllegalArgumentException e) { - throw new ThingsboardException("Action: " + strAction + " is not supported!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } + checkDashboardInfoId(dashboardId, Operation.READ); SecurityUser currentUser = getCurrentUser(); return userSettingsService.reportUserDashboardAction(currentUser.getTenantId(), currentUser.getId(), dashboardId, action); } + private void checkNotReserved(String strType, UserSettingsType type) throws ThingsboardException { + if (type.isReserved()) { + throw new ThingsboardException("Settings with type: " + strType + " are reserved for internal use!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java index 20a20a4bfc..3942cc6ad2 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.entitiy.user; import com.fasterxml.jackson.databind.JsonNode; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.HasTitle; @@ -32,12 +31,14 @@ import org.thingsboard.server.common.data.settings.StarredDashboardInfo; import org.thingsboard.server.common.data.settings.UserDashboardAction; import org.thingsboard.server.common.data.settings.UserDashboardsInfo; import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsType; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.user.UserSettingsService; import org.thingsboard.server.queue.util.TbCoreComponent; -import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -64,48 +65,33 @@ public class DefaultTbUserSettingsService implements TbUserSettingsService { } @Override - public void updateUserSettings(TenantId tenantId, UserId userId, JsonNode settings) { - updateUserSettings(tenantId, userId, UserSettings.GENERAL, settings); - } - - @Override - public void updateUserSettings(TenantId tenantId, UserId userId, String type, JsonNode settings) { + public void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings) { settingsService.updateUserSettings(tenantId, userId, type, settings); } @Override - public UserSettings findUserSettings(TenantId tenantId, UserId userId) { - return findUserSettings(tenantId, userId, UserSettings.GENERAL); - } - - @Override - public UserSettings findUserSettings(TenantId tenantId, UserId userId, String type) { + public UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type) { return settingsService.findUserSettings(tenantId, userId, type); } @Override - public void deleteUserSettings(TenantId tenantId, UserId userId, List jsonPaths) { - deleteUserSettings(tenantId, userId, UserSettings.GENERAL, jsonPaths); - } - - @Override - public void deleteUserSettings(TenantId tenantId, UserId userId, String type, List jsonPaths) { + public void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List jsonPaths) { settingsService.deleteUserSettings(tenantId, userId, type, jsonPaths); } @Override public UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id) { - UserSettings us = findUserSettings(tenantId, id, UserSettings.STARRED_DASHBOARDS); + UserSettings us = findUserSettings(tenantId, id, UserSettingsType.VISITED_DASHBOARDS); if (us == null) { return UserDashboardsInfo.EMPTY; } UserDashboardsInfo stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class); - return getUserDashboardsInfo(tenantId, stored); + return refreshDashboardTitles(tenantId, stored); } @Override public UserDashboardsInfo reportUserDashboardAction(TenantId tenantId, UserId id, DashboardId dashboardId, UserDashboardAction action) { - UserSettings us = findUserSettings(tenantId, id, UserSettings.STARRED_DASHBOARDS); + UserSettings us = findUserSettings(tenantId, id, UserSettingsType.VISITED_DASHBOARDS); UserDashboardsInfo stored = null; if (us != null) { stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class); @@ -126,24 +112,26 @@ public class DefaultTbUserSettingsService implements TbUserSettingsService { break; } + stored = refreshDashboardTitles(tenantId, stored); + us = new UserSettings(); us.setUserId(id); - us.setType(UserSettings.STARRED_DASHBOARDS); + us.setType(UserSettingsType.VISITED_DASHBOARDS); us.setSettings(JacksonUtil.valueToTree(stored)); saveUserSettings(tenantId, us); - return getUserDashboardsInfo(tenantId, stored); + return stored; } private void addToVisited(UserDashboardsInfo stored, DashboardId dashboardId) { UUID id = dashboardId.getId(); long ts = System.currentTimeMillis(); - var opt = stored.getLast().stream().filter(d -> id.equals(d.getId())).findFirst(); + var opt = stored.getLast().stream().filter(filterById(id)).findFirst(); if (opt.isPresent()) { opt.get().setLastVisited(ts); } else { var newInfo = new LastVisitedDashboardInfo(); newInfo.setId(id); - newInfo.setStarred(stored.getStarred().stream().anyMatch(d -> id.equals(d.getId()))); + newInfo.setStarred(stored.getStarred().stream().anyMatch(filterById(id))); newInfo.setLastVisited(System.currentTimeMillis()); stored.getLast().add(newInfo); } @@ -155,14 +143,14 @@ public class DefaultTbUserSettingsService implements TbUserSettingsService { private void removeFromStarred(UserDashboardsInfo stored, DashboardId dashboardId) { UUID id = dashboardId.getId(); - stored.getStarred().removeIf(d -> id.equals(d.getId())); + stored.getStarred().removeIf(filterById(id)); stored.getLast().stream().filter(d -> id.equals(d.getId())).findFirst().ifPresent(d -> d.setStarred(false)); } private void addToStarred(UserDashboardsInfo stored, DashboardId dashboardId) { UUID id = dashboardId.getId(); long ts = System.currentTimeMillis(); - var opt = stored.getStarred().stream().filter(d -> id.equals(d.getId())).findFirst(); + var opt = stored.getStarred().stream().filter(filterById(id)).findFirst(); if (opt.isPresent()) { opt.get().setStarredAt(ts); } else { @@ -180,31 +168,34 @@ public class DefaultTbUserSettingsService implements TbUserSettingsService { stored.getLast().forEach(d -> d.setStarred(starredMap.contains(d.getId()))); } - private void setTitleIfEmpty(TenantId tenantId, AbstractUserDashboardInfo i) { - if (StringUtils.isEmpty(i.getTitle())) { - var dashboardInfo = dashboardService.findDashboardInfoById(tenantId, new DashboardId(i.getId())); - i.setTitle(dashboardInfo != null ? dashboardInfo.getTitle() : null); - } + private Predicate filterById(UUID id) { + return d -> id.equals(d.getId()); } - private UserDashboardsInfo getUserDashboardsInfo(TenantId tenantId, UserDashboardsInfo stored) { + private UserDashboardsInfo refreshDashboardTitles(TenantId tenantId, UserDashboardsInfo stored) { if (stored == null) { return UserDashboardsInfo.EMPTY; } - - if (!stored.getLast().isEmpty()) { - stored.getLast().forEach(i -> setTitleIfEmpty(tenantId, i)); - stored.getLast().removeIf(EMPTY_TITLE); - } - if (!stored.getStarred().isEmpty()) { - Map lastMap = stored.getLast().stream().collect(Collectors.toMap(LastVisitedDashboardInfo::getId, Function.identity())); - stored.getStarred().forEach(i -> { - var last = lastMap.get(i.getId()); - i.setTitle(last != null ? last.getTitle() : null); - }); - stored.getStarred().forEach(i -> setTitleIfEmpty(tenantId, i)); - stored.getStarred().removeIf(EMPTY_TITLE); - } + stored.getLast().forEach(i -> i.setTitle(null)); + stored.getStarred().forEach(i -> i.setTitle(null)); + + Set uniqueIds = new HashSet<>(); + stored.getLast().stream().map(AbstractUserDashboardInfo::getId).forEach(uniqueIds::add); + stored.getStarred().stream().map(AbstractUserDashboardInfo::getId).forEach(uniqueIds::add); + + Map dashboardTitles = new HashMap<>(); + uniqueIds.forEach(id -> { + var dashboardInfo = dashboardService.findDashboardInfoById(tenantId, new DashboardId(id)); + if (dashboardInfo != null && StringUtils.isNotEmpty(dashboardInfo.getTitle())) { + dashboardTitles.put(id, dashboardInfo.getTitle()); + } + } + ); + + stored.getLast().forEach(i -> i.setTitle(dashboardTitles.get(i.getId()))); + stored.getLast().removeIf(EMPTY_TITLE); + stored.getStarred().forEach(i -> i.setTitle(dashboardTitles.get(i.getId()))); + stored.getStarred().removeIf(EMPTY_TITLE); return stored; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java index 6632e1977c..a81dbcad2f 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java @@ -22,24 +22,19 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.settings.UserDashboardAction; import org.thingsboard.server.common.data.settings.UserDashboardsInfo; import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsType; import java.util.List; public interface TbUserSettingsService { - void updateUserSettings(TenantId tenantId, UserId userId, JsonNode settings); - - void updateUserSettings(TenantId tenantId, UserId userId, String type, JsonNode settings); + void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings); UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings); - UserSettings findUserSettings(TenantId tenantId, UserId userId); - - UserSettings findUserSettings(TenantId tenantId, UserId userId, String type); - - void deleteUserSettings(TenantId tenantId, UserId userId, List jsonPaths); + UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type); - void deleteUserSettings(TenantId tenantId, UserId userId, String type, List jsonPaths); + void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List jsonPaths); UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index 84fab2d971..fd971fd72a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -1103,6 +1103,30 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { starred = newSettings.getStarred().get(0); Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId()); Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle()); + + doDelete("/api/dashboard/" + savedDashboard1.getId().getId().toString()).andExpect(status().isOk()); + + newSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class); + Assert.assertNotNull(newSettings); + Assert.assertNotNull(newSettings.getLast()); + Assert.assertEquals(1, newSettings.getLast().size()); + lastVisited = newSettings.getLast().get(0); + Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle()); + Assert.assertTrue(lastVisited.isStarred()); + Assert.assertEquals(1, newSettings.getStarred().size()); + starred = newSettings.getStarred().get(0); + Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId()); + Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle()); + + doDelete("/api/dashboard/" + savedDashboard2.getId().getId().toString()).andExpect(status().isOk()); + + retrievedSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class); + Assert.assertNotNull(retrievedSettings); + Assert.assertNotNull(retrievedSettings.getLast()); + Assert.assertTrue(retrievedSettings.getLast().isEmpty()); + Assert.assertNotNull(retrievedSettings.getStarred()); + Assert.assertTrue(retrievedSettings.getStarred().isEmpty()); } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java index 7c47efafc3..c6c87c07bf 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java @@ -36,6 +36,8 @@ public interface DashboardService extends EntityDaoService { DashboardInfo findDashboardInfoById(TenantId tenantId, DashboardId dashboardId); +// String findDashboardTitleById(TenantId tenantId, DashboardId dashboardId); + ListenableFuture findDashboardInfoByIdAsync(TenantId tenantId, DashboardId dashboardId); Dashboard saveDashboard(Dashboard dashboard); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java index c2531c5d65..58ac697409 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java @@ -19,17 +19,18 @@ import com.fasterxml.jackson.databind.JsonNode; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.settings.UserSettings; +import org.thingsboard.server.common.data.settings.UserSettingsType; import java.util.List; public interface UserSettingsService { - void updateUserSettings(TenantId tenantId, UserId userId, String type, JsonNode settings); + void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings); UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings); - UserSettings findUserSettings(TenantId tenantId, UserId userId, String type); + UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type); - void deleteUserSettings(TenantId tenantId, UserId userId, String type, List jsonPaths); + void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List jsonPaths); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java index f1318dedfd..3969c6d851 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java @@ -40,16 +40,13 @@ public class UserSettings implements Serializable { private static final long serialVersionUID = 2628320657987010348L; - public static final String GENERAL = "general"; - public static final String STARRED_DASHBOARDS = "starred_dashboards"; - @ApiModelProperty(position = 1, value = "JSON object with User id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private UserId userId; @ApiModelProperty(position = 2, value = "Type of the settings.") @NoXss @Length(fieldName = "type", max = 50) - private transient String type; + private UserSettingsType type; @ApiModelProperty(position = 3, value = "JSON object with user settings.", dataType = "com.fasterxml.jackson.databind.JsonNode") @NoXss diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java index 72fb432fd4..eba902fd7f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java @@ -34,7 +34,7 @@ public class UserSettingsCompositeKey implements Serializable { public UserSettingsCompositeKey(UserSettings userSettings) { this.userId = userSettings.getUserId().getId(); - this.type = userSettings.getType(); + this.type = userSettings.getType().name(); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java new file mode 100644 index 0000000000..97ef85a5f2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java @@ -0,0 +1,34 @@ +/** + * 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.common.data.settings; + +import lombok.Getter; + +public enum UserSettingsType { + + GENERAL, VISITED_DASHBOARDS(true), QUICK_LINKS, DOC_LINKS, DASHBOARDS; + + @Getter + private final boolean reserved; + + UserSettingsType() { + this.reserved = false; + } + + UserSettingsType(boolean reserved) { + this.reserved = reserved; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java index 1c38017097..a00efff52f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java @@ -23,6 +23,7 @@ import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; +import org.thingsboard.server.common.data.settings.UserSettingsType; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ToData; import org.thingsboard.server.dao.util.mapping.JsonStringType; @@ -54,7 +55,7 @@ public class UserSettingsEntity implements ToData { public UserSettingsEntity(UserSettings userSettings) { this.userId = userSettings.getUserId().getId(); - this.type = userSettings.getType(); + this.type = userSettings.getType().name(); if (userSettings.getSettings() != null) { this.settings = userSettings.getSettings(); } @@ -64,7 +65,7 @@ public class UserSettingsEntity implements ToData { public UserSettings toData() { UserSettings userSettings = new UserSettings(); userSettings.setUserId(new UserId(userId)); - userSettings.setType(type); + userSettings.setType(UserSettingsType.valueOf(type)); if (settings != null) { userSettings.setSettings(settings); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java index 88321faf66..86e63d3529 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; +import org.thingsboard.server.common.data.settings.UserSettingsType; import org.thingsboard.server.dao.entity.AbstractCachedService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.ConstraintValidator; @@ -55,11 +56,11 @@ public class UserSettingsServiceImpl extends AbstractCachedService userSettingsDao.findById(tenantId, key), true); } @Override - public void deleteUserSettings(TenantId tenantId, UserId userId, String type, List jsonPaths) { + public void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List jsonPaths) { log.trace("Executing deleteUserSettings for user [{}]", userId); validateId(userId, INCORRECT_USER_ID + userId); - var key = new UserSettingsCompositeKey(userId.getId(), type); + var key = new UserSettingsCompositeKey(userId.getId(), type.name()); UserSettings userSettings = userSettingsDao.findById(tenantId, key); if (userSettings == null) { return; diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java index d161af6b21..da38929ed7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; +import org.thingsboard.server.common.data.settings.UserSettingsType; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.service.AbstractServiceTest; import org.thingsboard.server.dao.user.UserDao; @@ -67,18 +68,18 @@ public class JpaUserSettingsDaoTest extends AbstractJpaDaoTest { public void testFindSettingsByUserId() { UserSettings userSettings = createUserSettings(user.getId()); - UserSettings retrievedUserSettings = userSettingsDao.findById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettings.GENERAL)); + UserSettings retrievedUserSettings = userSettingsDao.findById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettingsType.GENERAL.name())); assertEquals(retrievedUserSettings.getSettings(), userSettings.getSettings()); - userSettingsDao.removeById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettings.GENERAL)); + userSettingsDao.removeById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettingsType.GENERAL.name())); - UserSettings retrievedUserSettings2 = userSettingsDao.findById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettings.GENERAL)); + UserSettings retrievedUserSettings2 = userSettingsDao.findById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettingsType.GENERAL.name())); assertNull(retrievedUserSettings2); } private UserSettings createUserSettings(UserId userId) { UserSettings userSettings = new UserSettings(); - userSettings.setType(UserSettings.GENERAL); + userSettings.setType(UserSettingsType.GENERAL); userSettings.setSettings(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10))); userSettings.setUserId(userId); return userSettingsDao.save(SYSTEM_TENANT_ID, userSettings); From b0d177ee51253dd3a1f9dd1cdc703b7ea7e64c91 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 31 Mar 2023 17:43:49 +0300 Subject: [PATCH 13/18] Cache of the dashboard titles --- .../user/DefaultTbUserSettingsService.java | 6 +-- .../src/main/resources/thingsboard.yml | 4 ++ .../controller/BaseUserControllerTest.java | 25 ++++++++++++ .../dao/dashboard/DashboardService.java | 2 +- .../server/common/data/CacheConstants.java | 1 + .../dao/dashboard/DashboardInfoDao.java | 3 ++ .../dao/dashboard/DashboardServiceImpl.java | 39 ++++++++++++++++++- .../dashboard/DashboardTitleEvictEvent.java | 25 ++++++++++++ .../DashboardTitlesCaffeineCache.java | 33 ++++++++++++++++ .../dashboard/DashboardTitlesRedisCache.java | 35 +++++++++++++++++ .../dashboard/DashboardInfoRepository.java | 2 + .../sql/dashboard/JpaDashboardInfoDao.java | 5 +++ 12 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitleEvictEvent.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesCaffeineCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java index 3942cc6ad2..795ed1f145 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java @@ -185,9 +185,9 @@ public class DefaultTbUserSettingsService implements TbUserSettingsService { Map dashboardTitles = new HashMap<>(); uniqueIds.forEach(id -> { - var dashboardInfo = dashboardService.findDashboardInfoById(tenantId, new DashboardId(id)); - if (dashboardInfo != null && StringUtils.isNotEmpty(dashboardInfo.getTitle())) { - dashboardTitles.put(id, dashboardInfo.getTitle()); + var title = dashboardService.findDashboardTitleById(tenantId, new DashboardId(id)); + if (StringUtils.isNotEmpty(title)) { + dashboardTitles.put(id, title); } } ); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d96e50ca7f..e040e0bc5e 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -474,6 +474,10 @@ cache: userSettings: timeToLiveInMinutes: "${CACHE_SPECS_USER_SETTINGS_TTL:1440}" maxSize: "${CACHE_SPECS_USER_SETTINGS_MAX_SIZE:100000}" + dashboardTitles: + timeToLiveInMinutes: "${CACHE_SPECS_DASHBOARD_TITLES_TTL:1440}" + maxSize: "${CACHE_SPECS_DASHBOARD_TITLES_MAX_SIZE:100000}" + #Disable this because it is not required. spring.data.redis.repositories.enabled: false diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index fd971fd72a..680cf19822 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -18,6 +18,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Ignore; @@ -1104,6 +1105,30 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId()); Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle()); + //TEST renaming in the cache. + savedDashboard1.setTitle(RandomStringUtils.randomAlphanumeric(10)); + savedDashboard1 = doPost("/api/dashboard", savedDashboard1, Dashboard.class); + savedDashboard2.setTitle(RandomStringUtils.randomAlphanumeric(10)); + savedDashboard2 = doPost("/api/dashboard", savedDashboard2, Dashboard.class); + + newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/unstar", UserDashboardsInfo.class); + Assert.assertNotNull(newSettings); + Assert.assertNotNull(newSettings.getLast()); + Assert.assertEquals(2, newSettings.getLast().size()); + lastVisited = newSettings.getLast().get(0); + Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle()); + Assert.assertTrue(lastVisited.isStarred()); + lastVisited = newSettings.getLast().get(1); + Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId()); + Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle()); + Assert.assertFalse(lastVisited.isStarred()); + Assert.assertNotNull(retrievedSettings.getStarred()); + Assert.assertEquals(1, newSettings.getStarred().size()); + starred = newSettings.getStarred().get(0); + Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId()); + Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle()); + doDelete("/api/dashboard/" + savedDashboard1.getId().getId().toString()).andExpect(status().isOk()); newSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java index c6c87c07bf..c9974bba69 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java @@ -36,7 +36,7 @@ public interface DashboardService extends EntityDaoService { DashboardInfo findDashboardInfoById(TenantId tenantId, DashboardId dashboardId); -// String findDashboardTitleById(TenantId tenantId, DashboardId dashboardId); + String findDashboardTitleById(TenantId tenantId, DashboardId dashboardId); ListenableFuture findDashboardInfoByIdAsync(TenantId tenantId, DashboardId dashboardId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java index eedf925d61..b09d92bd19 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -42,4 +42,5 @@ public class CacheConstants { public static final String TWO_FA_VERIFICATION_CODES_CACHE = "twoFaVerificationCodes"; public static final String VERSION_CONTROL_TASK_CACHE = "versionControlTask"; public static final String USER_SETTINGS_CACHE = "userSettings"; + public static final String DASHBOARD_TITLES_CACHE = "dashboardTitles"; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java index e0d1777112..cb6a5050e5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java @@ -16,6 +16,8 @@ package org.thingsboard.server.dao.dashboard; import org.thingsboard.server.common.data.DashboardInfo; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; @@ -77,4 +79,5 @@ public interface DashboardInfoDao extends Dao { DashboardInfo findFirstByTenantIdAndName(UUID tenantId, String name); + String findTitleById(UUID tenantId, UUID dashboardId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index 96d56148eb..d1198d4d7a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -19,8 +19,12 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionalEventListener; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.thingsboard.server.cache.TbTransactionalCache; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; @@ -36,6 +40,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.edge.EdgeDao; import org.thingsboard.server.dao.entity.AbstractEntityService; @@ -43,6 +48,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.user.UserSettingsEvictEvent; import java.util.List; import java.util.Optional; @@ -70,6 +76,25 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @Autowired private DataValidator dashboardValidator; + @Autowired + protected TbTransactionalCache cache; + + @Autowired + private ApplicationEventPublisher eventPublisher; + + protected void publishEvictEvent(DashboardTitleEvictEvent event) { + if (TransactionSynchronizationManager.isActualTransactionActive()) { + eventPublisher.publishEvent(event); + } else { + handleEvictEvent(event); + } + } + + @TransactionalEventListener(classes = DashboardTitleEvictEvent.class) + public void handleEvictEvent(DashboardTitleEvictEvent event) { + cache.evict(event.getKey()); + } + @Override public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) { log.trace("Executing findDashboardById [{}]", dashboardId); @@ -91,6 +116,12 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb return dashboardInfoDao.findById(tenantId, dashboardId.getId()); } + @Override + public String findDashboardTitleById(TenantId tenantId, DashboardId dashboardId) { + return cache.getAndPutInTransaction(dashboardId, + () -> dashboardInfoDao.findTitleById(tenantId.getId(), dashboardId.getId()), true); + } + @Override public ListenableFuture findDashboardInfoByIdAsync(TenantId tenantId, DashboardId dashboardId) { log.trace("Executing findDashboardInfoByIdAsync [{}]", dashboardId); @@ -103,8 +134,13 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb log.trace("Executing saveDashboard [{}]", dashboard); dashboardValidator.validate(dashboard, DashboardInfo::getTenantId); try { - return dashboardDao.save(dashboard.getTenantId(), dashboard); + var saved = dashboardDao.save(dashboard.getTenantId(), dashboard); + publishEvictEvent(new DashboardTitleEvictEvent(saved.getId())); + return saved; } catch (Exception e) { + if (dashboard.getId() != null) { + publishEvictEvent(new DashboardTitleEvictEvent(dashboard.getId())); + } checkConstraintViolation(e, "dashboard_external_id_unq_key", "Dashboard with such external id already exists!"); throw e; } @@ -170,6 +206,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb deleteEntityRelations(tenantId, dashboardId); try { dashboardDao.removeById(tenantId, dashboardId.getId()); + publishEvictEvent(new DashboardTitleEvictEvent(dashboardId)); } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_default_dashboard_device_profile")) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitleEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitleEvictEvent.java new file mode 100644 index 0000000000..1e4e985988 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitleEvictEvent.java @@ -0,0 +1,25 @@ +/** + * 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.dao.dashboard; + +import lombok.Data; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; + +@Data +public class DashboardTitleEvictEvent { + private final DashboardId key; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesCaffeineCache.java new file mode 100644 index 0000000000..fca9a552a3 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesCaffeineCache.java @@ -0,0 +1,33 @@ +/** + * 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.dao.dashboard; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.id.DashboardId; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("DashboardTitlesCache") +public class DashboardTitlesCaffeineCache extends CaffeineTbTransactionalCache { + + public DashboardTitlesCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.DASHBOARD_TITLES_CACHE); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java new file mode 100644 index 0000000000..990a175b71 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java @@ -0,0 +1,35 @@ +/** + * 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.dao.dashboard; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CacheSpecsMap; +import org.thingsboard.server.cache.RedisTbTransactionalCache; +import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.id.DashboardId; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("DashboardTitlesCache") +public class DashboardTitlesRedisCache extends RedisTbTransactionalCache { + + public DashboardTitlesRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.DASHBOARD_TITLES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java index 0ede2325ac..735cffa5f7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java @@ -72,4 +72,6 @@ public interface DashboardInfoRepository extends JpaRepository Date: Fri, 31 Mar 2023 17:55:57 +0300 Subject: [PATCH 14/18] REST API doc improvements --- .../thingsboard/server/controller/UserController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index 311622db5d..fadb920938 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -499,7 +499,7 @@ public class UserController extends BaseController { "{A:5, B:{C:10, D:30}}. The same could be achieved by putting {B.D:30}") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PutMapping(value = "/user/settings/{type}") - public void putUserSettings(@ApiParam(value = "Settings type, one of: \"visit\", \"star\" or \"unstar\".") + public void putUserSettings(@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") @PathVariable("type") String strType, @RequestBody JsonNode settings) throws ThingsboardException { SecurityUser currentUser = getCurrentUser(); UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); @@ -511,7 +511,7 @@ public class UserController extends BaseController { notes = "Fetch the User settings based on authorized user. ") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @GetMapping(value = "/user/settings/{type}") - public JsonNode getUserSettings(@ApiParam(value = "Settings type, one of: \"visit\", \"star\" or \"unstar\".") + public JsonNode getUserSettings(@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") @PathVariable("type") String strType) throws ThingsboardException { SecurityUser currentUser = getCurrentUser(); UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); @@ -527,7 +527,7 @@ public class UserController extends BaseController { @RequestMapping(value = "/user/settings/{type}/{paths}", method = RequestMethod.DELETE) public void deleteUserSettings(@ApiParam(value = PATHS) @PathVariable(PATHS) String paths, - @ApiParam(value = "Settings type, one of: \"visit\", \"star\" or \"unstar\".") + @ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") @PathVariable("type") String strType) throws ThingsboardException { checkParameter(USER_ID, paths); UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); @@ -537,7 +537,7 @@ public class UserController extends BaseController { } @ApiOperation(value = "Get information about last visited and starred dashboards (getLastVisitedDashboards)", - notes = "Fetch the list of last visited and starred dashboards. Both lists are limited to 10 items.") + notes = "Fetch the list of last visited and starred dashboards. Both lists are limited to 10 items." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @GetMapping(value = "/user/dashboards") public UserDashboardsInfo getUserDashboardsInfo() throws ThingsboardException { @@ -546,7 +546,7 @@ public class UserController extends BaseController { } @ApiOperation(value = "Report action of User over the dashboard (reportUserDashboardAction)", - notes = "Enables or Disables user credentials. Useful when you would like to block user account without deleting it. " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH) + notes = "Report action of User over the dashboard. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/user/dashboards/{dashboardId}/{action}", method = RequestMethod.GET) @ResponseBody From 8d4f1e31dc6693c3d560ecc94453e94893dae046 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Mon, 3 Apr 2023 13:41:02 +0300 Subject: [PATCH 15/18] Code review updates - remove redundant alarmService.findById - use correct alarm service method based on msg type --- .../processor/alarm/BaseAlarmProcessor.java | 23 +++++++++++-------- .../server/edge/BaseAlarmEdgeTest.java | 17 ++++++++++++++ .../engine/edge/AbstractTbMsgPushNode.java | 17 +++++++++++++- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java index 84a768a886..be64f475a2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.EntityId; @@ -51,15 +52,13 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { switch (alarmUpdateMsg.getMsgType()) { case ENTITY_CREATED_RPC_MESSAGE: case ENTITY_UPDATED_RPC_MESSAGE: - Alarm alarm = alarmService.findAlarmById(tenantId, alarmId); - if (alarm == null) { - alarm = new Alarm(); - alarm.setTenantId(tenantId); - alarm.setType(alarmUpdateMsg.getName()); - alarm.setOriginator(originatorId); - alarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity())); - alarm.setStartTs(alarmUpdateMsg.getStartTs()); - } + Alarm alarm = new Alarm(); + alarm.setId(alarmId); + alarm.setTenantId(tenantId); + alarm.setType(alarmUpdateMsg.getName()); + alarm.setOriginator(originatorId); + alarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity())); + alarm.setStartTs(alarmUpdateMsg.getStartTs()); var alarmStatus = AlarmStatus.valueOf(alarmUpdateMsg.getStatus()); alarm.setClearTs(alarmUpdateMsg.getClearTs()); alarm.setPropagate(alarmUpdateMsg.getPropagate()); @@ -68,7 +67,11 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { alarm.setAckTs(alarmUpdateMsg.getAckTs()); alarm.setEndTs(alarmUpdateMsg.getEndTs()); alarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); - alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, null, alarmId)); + if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(alarmUpdateMsg.getMsgType())) { + alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, null, alarmId)); + } else { + alarmService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm)); + } return Futures.immediateFuture(null); case ALARM_ACK_RPC_MESSAGE: Alarm alarmToAck = alarmService.findAlarmById(tenantId, alarmId); diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java index 4c73128f3b..c06093535a 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.google.protobuf.AbstractMessage; import org.junit.Assert; import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.Alarm; @@ -91,12 +92,28 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest { Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); + Assert.assertEquals(savedAlarm.getUuidId().getMostSignificantBits(), alarmUpdateMsg.getIdMSB()); + Assert.assertEquals(savedAlarm.getUuidId().getLeastSignificantBits(), alarmUpdateMsg.getIdLSB()); Assert.assertEquals(savedAlarm.getType(), alarmUpdateMsg.getType()); Assert.assertEquals(savedAlarm.getName(), alarmUpdateMsg.getName()); Assert.assertEquals(device.getName(), alarmUpdateMsg.getOriginatorName()); Assert.assertEquals(savedAlarm.getStatus().name(), alarmUpdateMsg.getStatus()); Assert.assertEquals(savedAlarm.getSeverity().name(), alarmUpdateMsg.getSeverity()); + // update alarm + String updatedDetails = "{\"testKey\":\"testValue\"}"; + savedAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(updatedDetails)); + edgeImitator.expectMessageAmount(1); + savedAlarm = doPost("/api/alarm", savedAlarm, Alarm.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); + alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); + Assert.assertEquals(savedAlarm.getUuidId().getMostSignificantBits(), alarmUpdateMsg.getIdMSB()); + Assert.assertEquals(savedAlarm.getUuidId().getLeastSignificantBits(), alarmUpdateMsg.getIdLSB()); + Assert.assertEquals(updatedDetails, alarmUpdateMsg.getDetails()); + // ack alarm edgeImitator.expectMessageAmount(1); doPost("/api/alarm/" + savedAlarm.getUuidId() + "/ack"); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java index 6d8063b8b5..7be7dae2de 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java @@ -74,7 +74,8 @@ public abstract class AbstractTbMsgPushNode entityBody = new HashMap<>(); @@ -107,6 +108,20 @@ public abstract class AbstractTbMsgPushNode Date: Mon, 3 Apr 2023 13:42:19 +0300 Subject: [PATCH 16/18] Added cache spec for tests --- dao/src/test/resources/application-test.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index fc46ba431a..2d3b5197ae 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -68,6 +68,9 @@ cache.specs.edges.maxSize=100000 cache.specs.notificationRules.timeToLiveInMinutes=1440 cache.specs.notificationRules.maxSize=10000 +cache.specs.dashboardTitles.timeToLiveInMinutes=1440 +cache.specs.dashboardTitles.maxSize=10000 + redis.connection.host=localhost redis.connection.port=6379 redis.connection.db=0 From ad6f00716183ace30467fe368bf3388f272ee8fe Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 3 Apr 2023 18:46:50 +0300 Subject: [PATCH 17/18] Improvement to the System Info API --- .../system/DefaultSystemInfoService.java | 27 +++++------ .../controller/BaseHomePageApiTest.java | 5 +- common/cluster-api/src/main/proto/queue.proto | 9 ++-- .../server/common/data/SystemInfoData.java | 18 ++++---- .../DefaultTbServiceInfoProvider.java | 17 +++---- .../thingsboard/common/util/SystemUtil.java | 46 +++++++++---------- 6 files changed, 62 insertions(+), 60 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java index 8c4ca82783..caf3abefa0 100644 --- a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java @@ -30,7 +30,6 @@ import org.thingsboard.server.common.data.SystemInfo; import org.thingsboard.server.common.data.SystemInfoData; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; -import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; @@ -58,10 +57,9 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static org.thingsboard.common.util.SystemUtil.getCpuUsage; -import static org.thingsboard.common.util.SystemUtil.getFreeDiscSpace; -import static org.thingsboard.common.util.SystemUtil.getFreeMemory; import static org.thingsboard.common.util.SystemUtil.getMemoryUsage; -import static org.thingsboard.common.util.SystemUtil.getTotalCpuUsage; +import static org.thingsboard.common.util.SystemUtil.getDiscSpaceUsage; +import static org.thingsboard.common.util.SystemUtil.getCpuCount; import static org.thingsboard.common.util.SystemUtil.getTotalDiscSpace; import static org.thingsboard.common.util.SystemUtil.getTotalMemory; @@ -162,12 +160,13 @@ public class DefaultSystemInfoService extends TbApplicationEventListener tsList = new ArrayList<>(); - getMemoryUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", v)))); + + getCpuUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuUsage", (long) v)))); + getMemoryUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("memoryUsage", (long) v)))); + getDiscSpaceUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("discUsage", (long) v)))); + + getCpuCount().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("cpuCount", (long) v)))); getTotalMemory().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("totalMemory", v)))); - getFreeMemory().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("freeMemory", v)))); - getCpuUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new DoubleDataEntry("cpuUsage", v)))); - getTotalCpuUsage().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new DoubleDataEntry("totalCpuUsage", v)))); - getFreeDiscSpace().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("freeDiscSpace", v)))); getTotalDiscSpace().ifPresent(v -> tsList.add(new BasicTsKvEntry(ts, new LongDataEntry("totalDiscSpace", v)))); doSave(tsList); @@ -193,13 +192,15 @@ public class DefaultSystemInfoService extends TbApplicationEventListener 1 ? "MONOLITH" : serviceTypes.get(0)); + + infoData.setCpuUsage(serviceInfo.getSystemInfo().getCpuUsage()); infoData.setMemoryUsage(serviceInfo.getSystemInfo().getMemoryUsage()); + infoData.setDiscUsage(serviceInfo.getSystemInfo().getDiskUsage()); + + infoData.setCpuCount(serviceInfo.getSystemInfo().getCpuCount()); infoData.setTotalMemory(serviceInfo.getSystemInfo().getTotalMemory()); - infoData.setFreeMemory(serviceInfo.getSystemInfo().getFreeMemory()); - infoData.setCpuUsage(serviceInfo.getSystemInfo().getCpuUsage()); - infoData.setTotalCpuUsage(serviceInfo.getSystemInfo().getTotalCpuUsage()); - infoData.setFreeDiscSpace(serviceInfo.getSystemInfo().getFreeDiscSpace()); infoData.setTotalDiscSpace(serviceInfo.getSystemInfo().getTotalDiscSpace()); + return infoData; } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseHomePageApiTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseHomePageApiTest.java index 14fdfc4fac..f5904ecb82 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseHomePageApiTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseHomePageApiTest.java @@ -238,15 +238,16 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest { Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(apiUsageState.getId(), pageData.getData().get(0).getEntityId()); + List metrics = List.of("cpuUsage", "memoryUsage", "discUsage", "cpuCount", "totalMemory", "totalDiscSpace"); update = getWsClient().subscribeTsUpdate( - List.of("memoryUsage", "totalMemory", "freeMemory", "cpuUsage", "totalCpuUsage", "freeDiscSpace", "totalDiscSpace"), + metrics, now, TimeUnit.HOURS.toMillis(1)); Assert.assertEquals(1, update.getCmdId()); List listData = update.getUpdate(); Assert.assertNotNull(listData); Assert.assertEquals(1, listData.size()); Assert.assertEquals(apiUsageState.getId(), listData.get(0).getEntityId()); - Assert.assertEquals(7, listData.get(0).getTimeseries().size()); + Assert.assertEquals(metrics.size(), listData.get(0).getTimeseries().size()); for (TsValue[] tsv : listData.get(0).getTimeseries().values()) { Assert.assertTrue(tsv.length > 0); diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 4907d0f015..10d844ee78 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -31,13 +31,12 @@ message ServiceInfo { } message SystemInfoProto { - double cpuUsage = 1; - double totalCpuUsage = 2; + int64 cpuUsage = 1; + int64 cpuCount = 2; int64 memoryUsage = 3; int64 totalMemory = 4; - int64 freeMemory = 5; - int64 freeDiscSpace = 6; - int64 totalDiscSpace = 7; + int64 diskUsage = 5; + int64 totalDiscSpace = 6; } /** diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java b/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java index e4510e0f48..97845b789b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import org.thingsboard.server.common.data.kv.LongDataEntry; import java.util.Map; @@ -26,18 +27,17 @@ public class SystemInfoData { private String serviceId; @ApiModelProperty(position = 2, value = "Service type.") private String serviceType; - @ApiModelProperty(position = 3, value = "CPU usage.") - private Double cpuUsage; + @ApiModelProperty(position = 3, value = "CPU usage, in percent.") + private Long cpuUsage; @ApiModelProperty(position = 4, value = "Total CPU usage.") - private Double totalCpuUsage; - @ApiModelProperty(position = 5, value = "Memory usage in bytes.") + private Long cpuCount; + @ApiModelProperty(position = 5, value = "Memory usage, in percent.") private Long memoryUsage; @ApiModelProperty(position = 6, value = "Total memory in bytes.") private Long totalMemory; - @ApiModelProperty(position = 6, value = "Free memory in bytes.") - private Long freeMemory; - @ApiModelProperty(position = 7, value = "Free disc space in bytes.") - private Long freeDiscSpace; - @ApiModelProperty(position = 7, value = "Total disc space in bytes.") + @ApiModelProperty(position = 7, value = "Disk usage, in percent.") + private Long discUsage; + @ApiModelProperty(position = 8, value = "Total disc space in bytes.") private Long totalDiscSpace; + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java index 47e3af6a24..c48064a6b1 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java @@ -38,12 +38,13 @@ import java.util.List; import java.util.stream.Collectors; import static org.thingsboard.common.util.SystemUtil.getCpuUsage; -import static org.thingsboard.common.util.SystemUtil.getFreeDiscSpace; -import static org.thingsboard.common.util.SystemUtil.getFreeMemory; import static org.thingsboard.common.util.SystemUtil.getMemoryUsage; -import static org.thingsboard.common.util.SystemUtil.getTotalCpuUsage; -import static org.thingsboard.common.util.SystemUtil.getTotalDiscSpace; +import static org.thingsboard.common.util.SystemUtil.getDiscSpaceUsage; + +import static org.thingsboard.common.util.SystemUtil.getCpuCount; import static org.thingsboard.common.util.SystemUtil.getTotalMemory; +import static org.thingsboard.common.util.SystemUtil.getTotalDiscSpace; + @Component @Slf4j @@ -118,12 +119,12 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider { private TransportProtos.SystemInfoProto getCurrentSystemInfoProto() { TransportProtos.SystemInfoProto.Builder builder = TransportProtos.SystemInfoProto.newBuilder(); + getCpuUsage().ifPresent(builder::setCpuUsage); getMemoryUsage().ifPresent(builder::setMemoryUsage); + getDiscSpaceUsage().ifPresent(builder::setDiskUsage); + + getCpuCount().ifPresent(builder::setCpuCount); getTotalMemory().ifPresent(builder::setTotalMemory); - getFreeMemory().ifPresent(builder::setFreeMemory); - getCpuUsage().ifPresent(builder::setCpuUsage); - getTotalCpuUsage().ifPresent(builder::setTotalCpuUsage); - getFreeDiscSpace().ifPresent(builder::setFreeDiscSpace); getTotalDiscSpace().ifPresent(builder::setTotalDiscSpace); return builder.build(); diff --git a/common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java b/common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java index 686cfb28f2..b05ac87ef7 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java @@ -17,10 +17,13 @@ package org.thingsboard.common.util; import lombok.extern.slf4j.Slf4j; import oshi.SystemInfo; +import oshi.hardware.GlobalMemory; import oshi.hardware.HardwareAbstractionLayer; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.nio.file.FileStore; import java.nio.file.Files; import java.nio.file.Paths; @@ -32,14 +35,15 @@ public class SystemUtil { private static final HardwareAbstractionLayer HARDWARE; static { - SystemInfo si = new SystemInfo(); - HARDWARE = si.getHardware(); + HARDWARE = new SystemInfo().getHardware(); } - public static Optional getMemoryUsage() { + public static Optional getMemoryUsage() { try { - MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); - return Optional.of(memoryMXBean.getHeapMemoryUsage().getUsed()); + GlobalMemory memory = HARDWARE.getMemory(); + long total = memory.getTotal(); + long available = memory.getAvailable(); + return Optional.of(toPercent(total - available, total)); } catch (Exception e) { log.debug("Failed to get memory usage!!!", e); } @@ -55,37 +59,30 @@ public class SystemUtil { return Optional.empty(); } - public static Optional getFreeMemory() { + public static Optional getCpuUsage() { try { - return Optional.of(HARDWARE.getMemory().getAvailable()); - } catch (Exception e) { - log.debug("Failed to get free memory!!!", e); - } - return Optional.empty(); - } - - public static Optional getCpuUsage() { - try { - return Optional.of(prepare(HARDWARE.getProcessor().getSystemLoadAverage())); + return Optional.of((int) (HARDWARE.getProcessor().getSystemCpuLoad() * 100.0)); } catch (Exception e) { log.debug("Failed to get cpu usage!!!", e); } return Optional.empty(); } - public static Optional getTotalCpuUsage() { + public static Optional getCpuCount() { try { - return Optional.of(prepare(HARDWARE.getProcessor().getSystemCpuLoad() * 100)); + return Optional.of(HARDWARE.getProcessor().getLogicalProcessorCount()); } catch (Exception e) { - log.debug("Failed to get total cpu usage!!!", e); + log.debug("Failed to get total cpu count!!!", e); } return Optional.empty(); } - public static Optional getFreeDiscSpace() { + public static Optional getDiscSpaceUsage() { try { FileStore store = Files.getFileStore(Paths.get("/")); - return Optional.of(store.getUsableSpace()); + long total = store.getTotalSpace(); + long available = store.getUsableSpace(); + return Optional.of(toPercent(total - available, total)); } catch (Exception e) { log.debug("Failed to get free disc space!!!", e); } @@ -102,7 +99,10 @@ public class SystemUtil { return Optional.empty(); } - private static Double prepare(Double d) { - return (int) (d * 100) / 100.0; + private static int toPercent(long used, long total) { + BigDecimal u = new BigDecimal(used); + BigDecimal t = new BigDecimal(total); + BigDecimal i = new BigDecimal(100); + return u.multiply(i).divide(t, RoundingMode.HALF_UP).intValue(); } } From a4720bea8772f5f9af942b8d221e6a4e2b849027 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 3 Apr 2023 19:23:13 +0200 Subject: [PATCH 18/18] fixed upgrade script --- .../DefaultSystemDataLoaderService.java | 23 ++++++------------- .../service/install/InstallScripts.java | 18 +++++++-------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index a182a4d83f..bd68aad316 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -22,7 +22,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -91,6 +90,7 @@ import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.notification.NotificationSettingsService; +import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; @@ -177,6 +177,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private NotificationSettingsService notificationSettingsService; + @Autowired + private NotificationTargetService notificationTargetService; + @Bean protected BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -679,27 +682,15 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Override public void createDefaultNotificationConfigs() { - try { - log.info("Creating default notification configs for system admin"); + log.info("Creating default notification configs for system admin"); + if (notificationTargetService.findNotificationTargetsByTenantId(TenantId.SYS_TENANT_ID, new PageLink(1)).getTotalElements() == 0) { notificationSettingsService.createDefaultNotificationConfigs(TenantId.SYS_TENANT_ID); - } catch (Exception e) { - if (StringUtils.contains(e.getMessage(), "already exists")) { - log.info("Default notification configs are already present for system admin, skipping"); - } else { - throw e; - } } PageDataIterable tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500); log.info("Creating default notification configs for all tenants"); for (TenantId tenantId : tenants) { - try { + if (notificationTargetService.findNotificationTargetsByTenantId(tenantId, new PageLink(1)).getTotalElements() == 0) { notificationSettingsService.createDefaultNotificationConfigs(tenantId); - } catch (Exception e) { - if (StringUtils.contains(e.getMessage(), "already exists")) { - log.info("Default notification configs are already present for tenant {}, skipping", tenantId); - } else { - throw e; - } } } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 0ccb201cb9..de467f572a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -293,17 +293,15 @@ public class InstallScripts { } private void doSaveLwm2mResource(TbResource resource) throws ThingsboardException { - try { - log.trace("Executing saveResource [{}]", resource); - if (StringUtils.isEmpty(resource.getData())) { - throw new DataValidationException("Resource data should be specified!"); - } - toLwm2mResource(resource); + log.trace("Executing saveResource [{}]", resource); + if (StringUtils.isEmpty(resource.getData())) { + throw new DataValidationException("Resource data should be specified!"); + } + toLwm2mResource(resource); + TbResource foundResource = + resourceService.getResource(TenantId.SYS_TENANT_ID, ResourceType.LWM2M_MODEL, resource.getResourceKey()); + if (foundResource == null) { resourceService.saveResource(resource); - } catch (DataValidationException e) { - log.debug("[{}] {}", resource.getFileName(), e.getMessage()); - } catch (Exception ex) { - throw ex; } } }