Browse Source

Merge remote-tracking branch 'origin/improvements/notification-system' into improvements/notification-system

pull/8265/head
ViacheslavKlimov 3 years ago
parent
commit
a4ad8feb7f
  1. 77
      application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
  2. 9
      application/src/main/data/upgrade/3.4.4/schema_update.sql
  3. 13
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  4. 103
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  5. 23
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  6. 70
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
  7. 28
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java
  8. 66
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
  9. 202
      application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java
  10. 42
      application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java
  11. 23
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  12. 23
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  13. 27
      application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java
  14. 4
      application/src/main/resources/thingsboard.yml
  15. 5
      application/src/test/java/org/thingsboard/server/controller/BaseHomePageApiTest.java
  16. 188
      application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
  17. 23
      application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java
  18. 9
      common/cluster-api/src/main/proto/queue.proto
  19. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  20. 9
      common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java
  21. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  22. 18
      common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java
  23. 8
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java
  24. 37
      common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java
  25. 37
      common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java
  26. 36
      common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java
  27. 22
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardAction.java
  28. 46
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java
  29. 9
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java
  30. 44
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsCompositeKey.java
  31. 34
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java
  32. 17
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java
  33. 46
      common/util/src/main/java/org/thingsboard/common/util/SystemUtil.java
  34. 3
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java
  35. 39
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  36. 25
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitleEvictEvent.java
  37. 33
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesCaffeineCache.java
  38. 35
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java
  39. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  40. 13
      dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java
  41. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  42. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java
  43. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java
  44. 12
      dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java
  45. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java
  46. 7
      dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java
  47. 7
      dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java
  48. 3
      dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java
  49. 7
      dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java
  50. 43
      dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java
  51. 6
      dao/src/main/resources/sql/schema-entities.sql
  52. 3
      dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java
  53. 11
      dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java
  54. 3
      dao/src/test/resources/application-test.properties
  55. 17
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java
  56. 4
      ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts

77
application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json → application/src/main/data/json/edge/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": {
@ -46,8 +49,10 @@
"name": "Save Client Attributes",
"debugMode": false,
"configuration": {
"scope": "CLIENT_SCOPE"
}
"scope": "CLIENT_SCOPE",
"notifyDevice": "false"
},
"externalId": null
},
{
"additionalInfo": {
@ -59,7 +64,8 @@
"debugMode": false,
"configuration": {
"version": 0
}
},
"externalId": null
},
{
"additionalInfo": {
@ -73,7 +79,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 +94,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 +107,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 +155,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 +170,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

9
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;

13
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;
@ -146,6 +147,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;
@ -168,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;
@ -202,7 +205,7 @@ public abstract class BaseController {
protected UserService userService;
@Autowired
protected UserSettingsService userSettingsService;
protected TbUserSettingsService userSettingsService;
@Autowired
protected DeviceService deviceService;
@ -445,6 +448,14 @@ public abstract class BaseController {
}
}
protected <T> T checkEnumParameter(String name, String param, Function<String, T> 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);

103
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,9 +57,13 @@ 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.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;
@ -76,6 +82,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 +445,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(UserSettingsType.GENERAL);
userSettings.setSettings(settings);
userSettings.setUserId(currentUser.getId());
return userSettingsService.saveUserSettings(currentUser.getTenantId(), userSettings).getSettings();
@ -458,31 +466,108 @@ 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)",
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();
UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL);
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(",")));
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, 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);
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, 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);
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, 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);
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)",
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 {
SecurityUser currentUser = getCurrentUser();
return userSettingsService.findUserDashboardsInfo(currentUser.getTenantId(), currentUser.getId());
}
@ApiOperation(value = "Report action of User over the dashboard (reportUserDashboardAction)",
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
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);
UserDashboardAction action = checkEnumParameter("Action", strAction, UserDashboardAction::valueOf);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
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);
}
}
}

23
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -485,4 +485,27 @@ public abstract class BaseEdgeProcessor {
}
return customerId;
}
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:
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;
}
}
}

70
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java

@ -19,19 +19,19 @@ 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.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;
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;
@ -43,51 +43,59 @@ 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 = 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());
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.createOrUpdateAlarm(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()));
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:
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);

28
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;
}
}
}

66
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<ListenableFuture<Void>> result = new ArrayList<>();
EntityId entityId = constructEntityId(entityData.getEntityType(), entityData.getEntityIdMSB(), entityData.getEntityIdLSB());
if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg() || entityData.hasAttributesUpdatedMsg()) && entityId != null) {
Pair<TbMsgMetaData, CustomerId> 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<TbMsgMetaData, CustomerId> 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;
}

202
application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java

@ -0,0 +1,202 @@
/**
* 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.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.Comparator;
import java.util.HashMap;
import java.util.HashSet;
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;
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<HasTitle> EMPTY_TITLE = i -> StringUtils.isEmpty(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, UserSettingsType type, JsonNode settings) {
settingsService.updateUserSettings(tenantId, userId, type, settings);
}
@Override
public UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type) {
return settingsService.findUserSettings(tenantId, userId, type);
}
@Override
public void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List<String> jsonPaths) {
settingsService.deleteUserSettings(tenantId, userId, type, jsonPaths);
}
@Override
public UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id) {
UserSettings us = findUserSettings(tenantId, id, UserSettingsType.VISITED_DASHBOARDS);
if (us == null) {
return UserDashboardsInfo.EMPTY;
}
UserDashboardsInfo stored = JacksonUtil.convertValue(us.getSettings(), UserDashboardsInfo.class);
return refreshDashboardTitles(tenantId, stored);
}
@Override
public UserDashboardsInfo reportUserDashboardAction(TenantId tenantId, UserId id, DashboardId dashboardId, UserDashboardAction action) {
UserSettings us = findUserSettings(tenantId, id, UserSettingsType.VISITED_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;
}
stored = refreshDashboardTitles(tenantId, stored);
us = new UserSettings();
us.setUserId(id);
us.setType(UserSettingsType.VISITED_DASHBOARDS);
us.setSettings(JacksonUtil.valueToTree(stored));
saveUserSettings(tenantId, us);
return stored;
}
private void addToVisited(UserDashboardsInfo stored, DashboardId dashboardId) {
UUID id = dashboardId.getId();
long ts = System.currentTimeMillis();
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(filterById(id)));
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) {
UUID id = dashboardId.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(filterById(id)).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.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<UUID> starredMap =
stored.getStarred().stream().map(AbstractUserDashboardInfo::getId).collect(Collectors.toSet());
stored.getLast().forEach(d -> d.setStarred(starredMap.contains(d.getId())));
}
private Predicate<AbstractUserDashboardInfo> filterById(UUID id) {
return d -> id.equals(d.getId());
}
private UserDashboardsInfo refreshDashboardTitles(TenantId tenantId, UserDashboardsInfo stored) {
if (stored == null) {
return UserDashboardsInfo.EMPTY;
}
stored.getLast().forEach(i -> i.setTitle(null));
stored.getStarred().forEach(i -> i.setTitle(null));
Set<UUID> uniqueIds = new HashSet<>();
stored.getLast().stream().map(AbstractUserDashboardInfo::getId).forEach(uniqueIds::add);
stored.getStarred().stream().map(AbstractUserDashboardInfo::getId).forEach(uniqueIds::add);
Map<UUID, String> dashboardTitles = new HashMap<>();
uniqueIds.forEach(id -> {
var title = dashboardService.findDashboardTitleById(tenantId, new DashboardId(id));
if (StringUtils.isNotEmpty(title)) {
dashboardTitles.put(id, title);
}
}
);
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;
}
}

42
application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserSettingsService.java

@ -0,0 +1,42 @@
/**
* 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 org.thingsboard.server.common.data.settings.UserSettingsType;
import java.util.List;
public interface TbUserSettingsService {
void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings);
UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings);
UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type);
void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List<String> jsonPaths);
UserDashboardsInfo findUserDashboardsInfo(TenantId tenantId, UserId id);
UserDashboardsInfo reportUserDashboardAction(TenantId tenantId, UserId id, DashboardId dashboardId, UserDashboardAction action);
}

23
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<TenantId> 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;
}
}
}
}

23
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -65,6 +65,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";
@ -74,8 +75,6 @@ public class InstallScripts {
public static final String MODELS_LWM2M_DIR = "lwm2m-registry";
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";
@ -109,7 +108,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() {
@ -293,17 +292,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;
}
}
}

27
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<Partiti
private void saveCurrentMonolithSystemInfo() {
long ts = System.currentTimeMillis();
List<TsKvEntry> 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<Partiti
SystemInfoData infoData = new SystemInfoData();
infoData.setServiceId(serviceInfo.getServiceId());
infoData.setServiceType(serviceTypes.size() > 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;
}

4
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

5
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<String> 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<EntityData> 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);

188
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;
@ -31,6 +32,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 +44,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 +360,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 +578,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
String email1 = "testEmail1";
String email2 = "testEmail2";
List<User> customerUsersEmail1 = new ArrayList<>();
List<User> customerUsersEmail2= new ArrayList<>();
List<User> 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 +689,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 +864,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> 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 +916,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> usersInfo = getUsersInfo(pageLink);
List<UserEmailInfo> 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 +926,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 +945,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 +964,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 +981,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> loadedCustomerUsers = new ArrayList<>();
PageData<UserEmailInfo> 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 +991,167 @@ 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());
//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);
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());
}
}

23
application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java

@ -19,12 +19,14 @@ 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;
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 +35,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 +45,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 +71,7 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest {
Optional<AlarmInfo> 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());
@ -85,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");

9
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;
}
/**

2
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<DashboardInfo> findDashboardInfoByIdAsync(TenantId tenantId, DashboardId dashboardId);
Dashboard saveDashboard(Dashboard dashboard);

9
common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java

@ -18,18 +18,19 @@ 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 org.thingsboard.server.common.data.settings.UserSettingsType;
import java.util.List;
public interface UserSettingsService {
void updateUserSettings(TenantId tenantId, UserId userId, 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, UserSettingsType type);
void deleteUserSettings(TenantId tenantId, UserId userId, List<String> jsonPaths);
void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List<String> jsonPaths);
}

1
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";
}

18
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;
}

8
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();
}

37
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;
}

37
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;
}

36
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;
}

22
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
}

46
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<LastVisitedDashboardInfo> last;
@ApiModelProperty(position = 2, value = "List of starred dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private List<StarredDashboardInfo> starred;
public UserDashboardsInfo() {
this(new ArrayList<>(), new ArrayList<>());
}
}

9
common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java → 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;
@ -43,7 +43,12 @@ public class UserSettings implements Serializable {
@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 UserSettingsType 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;

44
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().name();
}
@Override
public String toString() {
return userId.toString() + "_" + type;
}
}

34
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;
}
}

17
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();

46
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<Long> getMemoryUsage() {
public static Optional<Integer> 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<Long> getFreeMemory() {
public static Optional<Integer> 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<Double> 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<Double> getTotalCpuUsage() {
public static Optional<Integer> 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<Long> getFreeDiscSpace() {
public static Optional<Integer> 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();
}
}

3
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> {
DashboardInfo findFirstByTenantIdAndName(UUID tenantId, String name);
String findTitleById(UUID tenantId, UUID dashboardId);
}

39
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<Dashboard> dashboardValidator;
@Autowired
protected TbTransactionalCache<DashboardId, String> 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<DashboardInfo> 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")) {

25
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;
}

33
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<DashboardId, String> {
public DashboardTitlesCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.DASHBOARD_TITLES_CACHE);
}
}

35
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<DashboardId, String> {
public DashboardTitlesRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.DASHBOARD_TITLES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
}
}

1
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";
/**

13
dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java

@ -21,7 +21,9 @@ 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.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;
@ -29,6 +31,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 +40,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<UserSettings> {
@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().name();
if (userSettings.getSettings() != null) {
this.settings= userSettings.getSettings();
this.settings = userSettings.getSettings();
}
}
@ -57,6 +65,7 @@ public class UserSettingsEntity implements ToData<UserSettings> {
public UserSettings toData() {
UserSettings userSettings = new UserSettings();
userSettings.setUserId(new UserId(userId));
userSettings.setType(UserSettingsType.valueOf(type));
if (settings != null) {
userSettings.setSettings(settings);
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -245,7 +245,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> 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(),

2
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java

@ -72,4 +72,6 @@ public interface DashboardInfoRepository extends JpaRepository<DashboardInfoEnti
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT di.title FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId AND di.id = :dashboardId")
String findTitleByTenantIdAndId(@Param("tenantId") UUID tenantId, @Param("dashboardId") UUID dashboardId);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java

@ -118,4 +118,9 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
public DashboardInfo findFirstByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(dashboardInfoRepository.findFirstByTenantIdAndTitle(tenantId, name));
}
@Override
public String findTitleById(UUID tenantId, UUID dashboardId) {
return dashboardInfoRepository.findTitleByTenantIdAndId(tenantId, dashboardId);
}
}

12
dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java

@ -19,8 +19,8 @@ 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.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 +41,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, UserSettingsCompositeKey id) {
return DaoUtil.getData(userSettingsRepository.findById(id));
}
@Override
public void removeById(TenantId tenantId, UserId userId) {
userSettingsRepository.deleteById(userId.getId());
public void removeById(TenantId tenantId, UserSettingsCompositeKey id) {
userSettingsRepository.deleteById(id);
}
}

3
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<UserSettingsEntity, UUID> {
public interface UserSettingsRepository extends JpaRepository<UserSettingsEntity, UserSettingsCompositeKey> {
}

7
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<UserId, UserSettings> {
public class UserSettingsCaffeineCache extends CaffeineTbTransactionalCache<UserSettingsCompositeKey, UserSettings> {
public UserSettingsCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.USER_SETTINGS_CACHE);

7
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);
}

3
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;
}

7
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<UserId, UserSettings> {
public class UserSettingsRedisCache extends RedisTbTransactionalCache<UserSettingsCompositeKey, UserSettings> {
public UserSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.USER_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());

43
dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java

@ -15,38 +15,36 @@
*/
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.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;
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<UserId, UserSettings, UserSettingsEvictEvent> implements UserSettingsService {
public class UserSettingsServiceImpl extends AbstractCachedService<UserSettingsCompositeKey, UserSettings, UserSettingsEvictEvent> implements UserSettingsService {
public static final String INCORRECT_USER_ID = "Incorrect userId ";
private final UserSettingsDao userSettingsDao;
@ -58,33 +56,37 @@ public class UserSettingsServiceImpl extends AbstractCachedService<UserId, UserS
}
@Override
public void updateUserSettings(TenantId tenantId, UserId userId, JsonNode settings) {
public void updateUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, JsonNode settings) {
log.trace("Executing updateUserSettings for user [{}], [{}]", userId, settings);
validateId(userId, INCORRECT_USER_ID + userId);
UserSettings oldSettings = userSettingsDao.findById(tenantId, userId);
var key = new UserSettingsCompositeKey(userId.getId(), type.name());
UserSettings oldSettings = userSettingsDao.findById(tenantId, key);
JsonNode oldSettingsJson = oldSettings != null ? oldSettings.getSettings() : JacksonUtil.newObjectNode();
UserSettings newUserSettings = new UserSettings();
newUserSettings.setUserId(userId);
newUserSettings.setType(type);
newUserSettings.setSettings(update(oldSettingsJson, settings));
doSaveUserSettings(tenantId, newUserSettings);
}
@Override
public UserSettings findUserSettings(TenantId tenantId, UserId userId) {
public UserSettings findUserSettings(TenantId tenantId, UserId userId, UserSettingsType type) {
log.trace("Executing findUserSettings for user [{}]", userId);
validateId(userId, INCORRECT_USER_ID + userId);
return cache.getAndPutInTransaction(userId,
() -> userSettingsDao.findById(tenantId, userId), true);
var key = new UserSettingsCompositeKey(userId.getId(), type.name());
return cache.getAndPutInTransaction(key,
() -> userSettingsDao.findById(tenantId, key), true);
}
@Override
public void deleteUserSettings(TenantId tenantId, UserId userId, List<String> jsonPaths) {
public void deleteUserSettings(TenantId tenantId, UserId userId, UserSettingsType type, List<String> 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.name());
UserSettings userSettings = userSettingsDao.findById(tenantId, key);
if (userSettings == null) {
return;
}
@ -95,7 +97,7 @@ public class UserSettingsServiceImpl extends AbstractCachedService<UserId, UserS
}
userSettings.setSettings(new ObjectMapper().readValue(dcSettings.jsonString(), ObjectNode.class));
} catch (Exception t) {
handleEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId()));
handleEvictEvent(new UserSettingsEvictEvent(key));
throw new RuntimeException(t);
}
doSaveUserSettings(tenantId, userSettings);
@ -103,12 +105,13 @@ public class UserSettingsServiceImpl extends AbstractCachedService<UserId, UserS
private UserSettings doSaveUserSettings(TenantId tenantId, UserSettings userSettings) {
try {
ConstraintValidator.validateFields(userSettings);
validateJsonKeys(userSettings.getSettings());
UserSettings saved = userSettingsDao.save(tenantId, userSettings);
publishEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId()));
publishEvictEvent(new UserSettingsEvictEvent(new UserSettingsCompositeKey(userSettings)));
return saved;
} catch (Exception t) {
handleEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId()));
handleEvictEvent(new UserSettingsEvictEvent(new UserSettingsCompositeKey(userSettings)));
throw t;
}
}
@ -116,9 +119,7 @@ public class UserSettingsServiceImpl extends AbstractCachedService<UserId, UserS
@TransactionalEventListener(classes = UserSettingsEvictEvent.class)
@Override
public void handleEvictEvent(UserSettingsEvictEvent event) {
List<UserId> keys = new ArrayList<>();
keys.add(event.getUserId());
cache.evict(keys);
cache.evict(event.getKey());
}
private void validateJsonKeys(JsonNode userSettings) {

6
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;

3
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;

11
dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java

@ -27,7 +27,9 @@ 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.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;
@ -66,17 +68,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, new UserSettingsCompositeKey(user.getId().getId(), UserSettingsType.GENERAL.name()));
assertEquals(retrievedUserSettings.getSettings(), userSettings.getSettings());
userSettingsDao.removeById(SYSTEM_TENANT_ID, user.getId());
userSettingsDao.removeById(SYSTEM_TENANT_ID, new UserSettingsCompositeKey(user.getId().getId(), UserSettingsType.GENERAL.name()));
UserSettings retrievedUserSettings2 = userSettingsDao.findById(SYSTEM_TENANT_ID, user.getId());
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(UserSettingsType.GENERAL);
userSettings.setSettings(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10)));
userSettings.setUserId(userId);
return userSettingsDao.save(SYSTEM_TENANT_ID, userSettings);

3
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

17
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java

@ -74,7 +74,8 @@ public abstract class AbstractTbMsgPushNode<T extends BaseTbMsgPushNodeConfigura
protected S buildEvent(TbMsg msg, TbContext ctx) {
String msgType = msg.getType();
if (DataConstants.ALARM.equals(msgType)) {
return buildEvent(ctx.getTenantId(), EdgeEventActionType.ADDED, getUUIDFromMsgData(msg), getAlarmEventType(), null);
EdgeEventActionType actionType = getAlarmActionType(msg);
return buildEvent(ctx.getTenantId(), actionType, getUUIDFromMsgData(msg), getAlarmEventType(), null);
} else {
EdgeEventActionType actionType = getEdgeEventActionTypeByMsgType(msgType);
Map<String, Object> entityBody = new HashMap<>();
@ -107,6 +108,20 @@ public abstract class AbstractTbMsgPushNode<T extends BaseTbMsgPushNodeConfigura
}
}
private static EdgeEventActionType getAlarmActionType(TbMsg msg) {
boolean isNewAlarm = Boolean.parseBoolean(msg.getMetaData().getValue(DataConstants.IS_NEW_ALARM));
boolean isClearedAlarm = Boolean.parseBoolean(msg.getMetaData().getValue(DataConstants.IS_CLEARED_ALARM));
EdgeEventActionType eventAction;
if (isNewAlarm) {
eventAction = EdgeEventActionType.ADDED;
} else if (isClearedAlarm) {
eventAction = EdgeEventActionType.ALARM_CLEAR;
} else {
eventAction = EdgeEventActionType.UPDATED;
}
return eventAction;
}
abstract S buildEvent(TenantId tenantId, EdgeEventActionType eventAction, UUID entityId, U eventType, JsonNode entityBody);
abstract U getEventTypeByEntityType(EntityType entityType);

4
ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts

@ -142,11 +142,11 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
);
} else if (ruleChainScope === 'edges') {
columns.push(
new EntityTableColumn<RuleChain>('root', 'rulechain.edge-template-root', '60px',
new EntityTableColumn<RuleChain>('root', 'rulechain.edge-template-root', '70px',
entity => {
return checkBoxCell(entity.root);
}),
new EntityTableColumn<RuleChain>('assignToEdge', 'rulechain.assign-to-edge', '60px',
new EntityTableColumn<RuleChain>('assignToEdge', 'rulechain.assign-to-edge', '70px',
entity => {
return checkBoxCell(this.isAutoAssignToEdgeRuleChain(entity));
})

Loading…
Cancel
Save