Browse Source

Merge branch 'alarmComments' of github.com:dashevchenko/thingsboard into develop/3.5

feature/deduplication-node
Andrii Shvaika 4 years ago
parent
commit
ed4ce8e6d0
  1. 26
      application/src/main/data/upgrade/3.4.3/schema_update.sql
  2. 5
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  3. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  4. 129
      application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java
  5. 17
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  6. 3
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  7. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
  8. 11
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  9. 5
      application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java
  10. 52
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java
  11. 28
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java
  12. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java
  13. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  14. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
  15. 62
      application/src/main/java/org/thingsboard/server/service/ttl/AlarmCommentsCleanUpService.java
  16. 7
      application/src/main/resources/thingsboard.yml
  17. 8
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  18. 369
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
  19. 23
      application/src/test/java/org/thingsboard/server/controller/sql/AlarmCommentControllerSqlTest.java
  20. 3
      application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
  21. 96
      application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java
  22. 36
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentOperationResult.java
  23. 38
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java
  24. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  25. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  26. 87
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java
  27. 52
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentInfo.java
  28. 45
      common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmCommentId.java
  29. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  30. 43
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentDao.java
  31. 127
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java
  32. 8
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  33. 99
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java
  34. 55
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentEntity.java
  35. 53
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentInfoEntity.java
  36. 23
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  37. 38
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmCommentDataValidator.java
  38. 39
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmCommentRepository.java
  39. 106
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDao.java
  40. 2
      dao/src/main/resources/sql/schema-entities-idx.sql
  41. 10
      dao/src/main/resources/sql/schema-entities.sql
  42. 3
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  43. 162
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java
  44. 23
      dao/src/test/java/org/thingsboard/server/dao/service/sql/AlarmCommentServiceSqlTest.java
  45. 90
      dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java
  46. 3
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  47. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java
  48. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java
  49. 13
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java

26
application/src/main/data/upgrade/3.4.3/schema_update.sql

@ -0,0 +1,26 @@
--
-- Copyright © 2016-2022 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.
--
CREATE TABLE IF NOT EXISTS alarm_comment (
id uuid NOT NULL,
created_time bigint NOT NULL,
alarm_id uuid NOT NULL,
user_id uuid,
type varchar(255) NOT NULL,
comment varchar(1000000),
CONSTRAINT fk_alarm_comment_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE
) PARTITION BY RANGE (created_time);
CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id);

5
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
@ -278,6 +279,10 @@ public class ActorSystemContext {
@Getter
private AlarmSubscriptionService alarmService;
@Autowired
@Getter
private AlarmCommentService alarmCommentService;
@Autowired
@Getter
private JsInvokeService jsInvokeService;

6
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -71,6 +71,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.TbMsgProcessingStackItem;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
@ -591,6 +592,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getAlarmService();
}
@Override
public AlarmCommentService getAlarmCommentService() {
return mainCtx.getAlarmCommentService();
}
@Override
public RuleChainService getRuleChainService() {
return mainCtx.getRuleChainService();

129
application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java

@ -0,0 +1,129 @@
/**
* Copyright © 2016-2022 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.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmCommentId;
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.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import static org.thingsboard.server.controller.ControllerConstants.ALARM_COMMENT_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ALARM_SORT_PROPERTY_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK;
@RestController
@TbCoreComponent
@RequiredArgsConstructor
@RequestMapping("/api")
public class AlarmCommentController extends BaseController {
public static final String ALARM_ID = "alarmId";
public static final String ALARM_COMMENT_ID = "commentId";
private final TbAlarmCommentService tbAlarmCommentService;
@ApiOperation(value = "Create or update Alarm Comment ",
notes = "Creates or Updates the Alarm Comment. " +
"When creating comment, platform generates Alarm Comment Id as " + UUID_WIKI_LINK +
"The newly created Alarm Comment id will be present in the response. Specify existing Alarm Comment id to update the alarm. " +
"Referencing non-existing Alarm Comment Id will cause 'Not Found' error. " +
"\n\n To create new Alarm comment entity it is enough to specify 'comment' json element with 'text' node, for example: {\"comment\": { \"text\": \"my comment\"}}. " +
"\n\n If comment type is not specified the default value 'OTHER' will be saved. If 'alarmId' or 'userId' specified in body it will be ignored." +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.POST)
@ResponseBody
public AlarmComment saveAlarmComment(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION)
@PathVariable(ALARM_ID) String strAlarmId, @ApiParam(value = "A JSON value representing the comment.") @RequestBody AlarmComment alarmComment) throws ThingsboardException {
checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
alarmComment.setAlarmId(alarmId);
return tbAlarmCommentService.saveAlarmComment(alarm, alarmComment, getCurrentUser());
}
@ApiOperation(value = "Delete Alarm comment (deleteAlarmComment)",
notes = "Deletes the Alarm comment. Referencing non-existing Alarm comment Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/comment/{commentId}", method = RequestMethod.DELETE)
@ResponseBody
public Boolean deleteAlarmComment(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId, @ApiParam(value = ALARM_COMMENT_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_COMMENT_ID) String strCommentId) throws ThingsboardException {
checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.DELETE);
AlarmCommentId alarmCommentId = new AlarmCommentId(toUUID(strCommentId));
AlarmComment alarmComment = checkAlarmCommentId(alarmCommentId);
return tbAlarmCommentService.deleteAlarmComment(alarm, alarmComment, getCurrentUser());
}
@ApiOperation(value = "Get Alarm comments (getAlarmComments)",
notes = "Returns a page of alarm comments for specified alarm. " +
PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.GET)
@ResponseBody
public PageData<AlarmCommentInfo> getAlarmComments(
@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION)
@PathVariable(ALARM_ID) String strAlarmId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder
) throws Exception {
checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = alarmService.findAlarmByIdAsync(getCurrentUser().getTenantId(), alarmId).get();
checkNotNull(alarm, "Alarm with id [" + alarmId + "] is not found");
checkEntityId(alarm.getOriginator(), Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder);
return checkNotNull(alarmCommentService.findAlarmComments(alarm.getTenantId(), alarmId, pageLink));
}
}

17
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -54,6 +54,7 @@ import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
@ -64,6 +65,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.edge.EdgeInfo;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
@ -98,6 +100,7 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
@ -200,6 +203,9 @@ public abstract class BaseController {
@Autowired
protected AlarmSubscriptionService alarmService;
@Autowired
protected AlarmCommentService alarmCommentService;
@Autowired
protected DeviceCredentialsService deviceCredentialsService;
@ -716,6 +722,17 @@ public abstract class BaseController {
}
}
AlarmComment checkAlarmCommentId(AlarmCommentId alarmCommentId) throws ThingsboardException {
try {
validateId(alarmCommentId, "Incorrect alarmCommentId " + alarmCommentId);
AlarmComment alarmComment = alarmCommentService.findAlarmCommentByIdAsync(getCurrentUser().getTenantId(), alarmCommentId).get();
checkNotNull(alarmComment, "Alarm comment with id [" + alarmCommentId + "] is not found");
return alarmComment;
} catch (Exception e) {
throw handleException(e, false);
}
}
WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, Operation operation) throws ThingsboardException {
try {
validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId);

3
application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java

@ -44,6 +44,8 @@ public class ControllerConstants {
protected static final String USER_ID_PARAM_DESCRIPTION = "A string value representing the user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ASSET_ID_PARAM_DESCRIPTION = "A string value representing the asset id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ALARM_ID_PARAM_DESCRIPTION = "A string value representing the alarm id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ALARM_COMMENT_ID_PARAM_DESCRIPTION = "A string value representing the alarm comment id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ENTITY_ID_PARAM_DESCRIPTION = "A string value representing the entity id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String OTA_PACKAGE_ID_PARAM_DESCRIPTION = "A string value representing the ota package id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ENTITY_TYPE_PARAM_DESCRIPTION = "A string value representing the entity type. For example, 'DEVICE'";
@ -100,6 +102,7 @@ public class ControllerConstants {
protected static final String ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, description, isDefault";
protected static final String ASSET_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle";
protected static final String ALARM_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, startTs, endTs, type, ackTs, clearTs, severity, status";
protected static final String ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime";
protected static final String EVENT_SORT_PROPERTY_ALLOWABLE_VALUES = "ts, id";
protected static final String EDGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle";
protected static final String RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, root";

3
application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java

@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -65,6 +66,8 @@ public abstract class AbstractTbEntityService {
@Autowired
protected AlarmSubscriptionService alarmSubscriptionService;
@Autowired
protected AlarmCommentService alarmCommentService;
@Autowired
protected CustomerService customerService;
@Autowired
protected TbClusterService tbClusterService;

11
application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -229,6 +230,16 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
sendEntityNotificationMsg(alarm.getTenantId(), alarm.getId(), edgeTypeByActionType(actionType));
}
@Override
public void notifyCreateOrUpdateAlarmComment(Alarm alarm, AlarmComment alarmComment, ActionType actionType, User user) {
logEntityAction(alarm.getTenantId(), alarmComment.getId(), alarmComment, alarm.getCustomerId(), actionType, user);
}
@Override
public void notifyDeleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) {
logEntityAction(alarm.getTenantId(), alarmComment.getId(), alarmComment, alarm.getCustomerId(), ActionType.DELETED, user);
}
@Override
public <E extends HasName, I extends EntityId> void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId,
I entityId, E entity, User user,

5
application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -102,6 +103,10 @@ public interface TbNotificationEntityService {
void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo);
void notifyCreateOrUpdateAlarmComment(Alarm alarm, AlarmComment alarmComment, ActionType actionType, User user);
void notifyDeleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user);
<E extends HasName, I extends EntityId> void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId,
I entityId, E entity, User user,
ActionType actionType, boolean sendNotifyMsgToEdge,

52
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java

@ -0,0 +1,52 @@
/**
* Copyright © 2016-2022 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.alarm;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@Service
@AllArgsConstructor
public class DefaultTbAlarmCommentService extends AbstractTbEntityService implements TbAlarmCommentService{
@Override
public AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException {
ActionType actionType = alarmComment.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
UserId userId = user.getId();
alarmComment.setUserId(userId);
try {
AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment).getAlarmComment());
notificationEntityService.notifyCreateOrUpdateAlarmComment(alarm, savedAlarmComment, actionType, user);
return savedAlarmComment;
} catch (Exception e) {
notificationEntityService.logEntityAction(alarm.getTenantId(), emptyId(EntityType.ALARM_COMMENT), alarmComment, actionType, user, e);
throw e;
}
}
@Override
public Boolean deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) {
notificationEntityService.notifyDeleteAlarmComment(alarm, alarmComment, user);
return alarmCommentService.deleteAlarmComment(alarm.getTenantId(), alarmComment.getId()).isSuccessful();
}
}

28
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2022 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.alarm;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
public interface TbAlarmCommentService {
AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException;
Boolean deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user);
}

1
application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java

@ -32,6 +32,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
public CustomerUserPermissions() {
super();
put(Resource.ALARM, customerAlarmPermissionChecker);
put(Resource.ALARM_COMMENT, customerAlarmPermissionChecker);
put(Resource.ASSET, customerEntityPermissionChecker);
put(Resource.DEVICE, customerEntityPermissionChecker);
put(Resource.CUSTOMER, customerPermissionChecker);

1
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -22,6 +22,7 @@ import java.util.Optional;
public enum Resource {
ADMIN_SETTINGS(),
ALARM(EntityType.ALARM),
ALARM_COMMENT(EntityType.ALARM_COMMENT),
DEVICE(EntityType.DEVICE),
ASSET(EntityType.ASSET),
CUSTOMER(EntityType.CUSTOMER),

1
application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java

@ -30,6 +30,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
super();
put(Resource.ADMIN_SETTINGS, PermissionChecker.allowAllPermissionChecker);
put(Resource.ALARM, tenantEntityPermissionChecker);
put(Resource.ALARM_COMMENT, tenantEntityPermissionChecker);
put(Resource.ASSET, tenantEntityPermissionChecker);
put(Resource.DEVICE, tenantEntityPermissionChecker);
put(Resource.CUSTOMER, tenantEntityPermissionChecker);

62
application/src/main/java/org/thingsboard/server/service/ttl/AlarmCommentsCleanUpService.java

@ -0,0 +1,62 @@
/**
* Copyright © 2016-2022 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.ttl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.alarm.AlarmCommentDao;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import org.thingsboard.server.queue.discovery.PartitionService;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COMMENT_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_COLUMN_FAMILY_NAME;
@Service
@ConditionalOnExpression("${sql.ttl.alarm_comments.enabled:true} && ${sql.ttl.alarm_comments.ttl:0} > 0")
@Slf4j
public class AlarmCommentsCleanUpService extends AbstractCleanUpService {
private final AlarmCommentDao alarmCommentDao;
private final SqlPartitioningRepository partitioningRepository;
@Value("${sql.ttl.alarm_comments.ttl:0}")
private long ttlInSec;
@Value("${sql.alarm_comments.partition_size:168}")
private int partitionSizeInHours;
public AlarmCommentsCleanUpService(PartitionService partitionService, AlarmCommentDao alarmCommentDao, SqlPartitioningRepository partitioningRepository) {
super(partitionService);
this.alarmCommentDao = alarmCommentDao;
this.partitioningRepository = partitioningRepository;
}
@Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.alarm_comments.checking_interval_ms})}",
fixedDelayString = "${sql.ttl.alarm_comments.checking_interval_ms}")
public void cleanUp() {
long commentsExpTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec);
if (isSystemTenantPartitionMine()) {
alarmCommentDao.cleanUpAlarmComments(commentsExpTime);
} else {
partitioningRepository.cleanupPartitionsCache(ALARM_COMMENT_COLUMN_FAMILY_NAME, commentsExpTime, TimeUnit.HOURS.toMillis(partitionSizeInHours));
}
}
}

7
application/src/main/resources/thingsboard.yml

@ -277,6 +277,8 @@ sql:
partition_size: "${SQL_EDGE_EVENTS_PARTITION_SIZE_HOURS:168}" # Number of hours to partition the events. The current value corresponds to one week.
audit_logs:
partition_size: "${SQL_AUDIT_LOGS_PARTITION_SIZE_HOURS:168}" # Default value - 1 week
alarm_comments:
partition_size: "${SQL_ALARM_COMMENTS_PARTITION_SIZE_HOURS:168}" # Default value - 1 week
# Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
batch_sort: "${SQL_BATCH_SORT:true}"
# Specify whether to remove null characters from strValue of attributes and timeseries before insert
@ -312,6 +314,10 @@ sql:
alarms:
checking_interval: "${SQL_ALARMS_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours
removal_batch_size: "${SQL_ALARMS_TTL_REMOVAL_BATCH_SIZE:3000}" # To delete outdated alarms not all at once but in batches
alarms_comments:
enabled: "${SQL_TTL_ALARM_COMMENTS_ENABLED:true}"
ttl: "${SQL_TTL_ALARM_COMMENTS_SECS:0}" # Disabled by default. Accuracy of the cleanup depends on the sql.alarm_comments.partition_size
checking_interval_ms: "${SQL_TTL_ALARM_COMMENTS_CHECKING_INTERVAL_MS:86400000}" # Default value - 1 day
rpc:
enabled: "${SQL_TTL_RPC_ENABLED:true}"
checking_interval: "${SQL_RPC_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours
@ -582,6 +588,7 @@ audit-log:
"user": "${AUDIT_LOG_MASK_USER:W}"
"rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}"
"alarm": "${AUDIT_LOG_MASK_ALARM:W}"
"alarm_comment": "${AUDIT_LOG_MASK_ALARM_COMMENT:W}"
"entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}"
"device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}"
"asset_profile": "${AUDIT_LOG_MASK_ASSET_PROFILE:W}"

8
application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java

@ -340,6 +340,12 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.argThat(matcherOriginatorId), Mockito.any(TbMsg.class), Mockito.isNull());
}
protected void testPushMsgToRuleEngineTime(EntityId matcherOriginatorId, TenantId tenantId, HasName entity, int cntTime) {
tenantId = tenantId.isNullUid() && ((HasTenantId) entity).getTenantId() != null ? ((HasTenantId) entity).getTenantId() : tenantId;
Mockito.verify(tbClusterService, times(cntTime)).pushMsgToRuleEngine(Mockito.eq(tenantId),
Mockito.eq(matcherOriginatorId), Mockito.any(TbMsg.class), Mockito.isNull());
}
private void testNotificationMsgToEdgeServiceTime(EntityId entityId, TenantId tenantId, ActionType actionType, int cntTime) {
EdgeEventActionType edgeEventActionType = ActionType.CREDENTIALS_UPDATED.equals(actionType) ?
EdgeEventActionType.CREDENTIALS_UPDATED : edgeTypeByActionType(actionType);
@ -367,7 +373,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.verify(tbClusterService, times(cntTime)).pushMsgToCore(Mockito.any(ToDeviceActorNotificationMsg.class), Mockito.isNull());
}
private void testLogEntityAction(HasName entity, EntityId originatorId, TenantId tenantId,
protected void testLogEntityAction(HasName entity, EntityId originatorId, TenantId tenantId,
CustomerId customerId, UserId userId, String userName,
ActionType actionType, int cntTime, Object... additionalInfo) {
ArgumentMatcher<HasName> matcherEntityEquals = entity == null ? Objects::isNull : argument -> argument.toString().equals(entity.toString());

369
application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java

@ -0,0 +1,369 @@
/**
* Copyright © 2016-2022 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.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.TextNode;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.dao.alarm.AlarmDao;
import java.util.LinkedList;
import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@ContextConfiguration(classes = {BaseAlarmCommentControllerTest.Config.class})
public abstract class BaseAlarmCommentControllerTest extends AbstractControllerTest {
protected Device customerDevice;
protected Alarm alarm;
static class Config {
@Bean
@Primary
public AlarmDao alarmDao(AlarmDao alarmDao) {
return Mockito.mock(AlarmDao.class, AdditionalAnswers.delegatesTo(alarmDao));
}
}
@Before
public void setup() throws Exception {
loginTenantAdmin();
Device device = new Device();
device.setTenantId(tenantId);
device.setName("Test device");
device.setLabel("Label");
device.setType("Type");
device.setCustomerId(customerId);
customerDevice = doPost("/api/device", device, Device.class);
alarm = Alarm.builder()
.tenantId(tenantId)
.customerId(customerId)
.originator(customerDevice.getId())
.status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type("test alarm type")
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
resetTokens();
}
@After
public void teardown() throws Exception {
Mockito.reset(tbClusterService, auditLogService);
loginSysAdmin();
deleteDifferentTenant();
}
@Test
public void testCreateAlarmCommentViaCustomer() throws Exception {
loginCustomerUser();
Mockito.reset(tbClusterService, auditLogService);
AlarmComment createdComment = createAlarmComment(alarm.getId());
testLogEntityAction(createdComment, createdComment.getId(), tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ADDED, 1);
testPushMsgToRuleEngineTime(createdComment.getId(), tenantId, createdComment, 1);
}
@Test
public void testCreateAlarmCommentViaTenant() throws Exception {
loginTenantAdmin();
Mockito.reset(tbClusterService, auditLogService);
AlarmComment createdComment = createAlarmComment(alarm.getId());
Assert.assertEquals("OTHER", createdComment.getType());
testLogEntityAction(createdComment, createdComment.getId(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ADDED, 1);
testPushMsgToRuleEngineTime(createdComment.getId(), tenantId, createdComment, 1);
}
@Test
public void testUpdateAlarmCommentViaCustomer() throws Exception {
loginCustomerUser();
AlarmComment savedComment = createAlarmComment(alarm.getId());
Mockito.reset(tbClusterService, auditLogService);
JsonNode newComment = JacksonUtil.newObjectNode().set("text", new TextNode("Updated comment"));
savedComment.setComment(newComment);
AlarmComment updatedAlarmComment = saveAlarmComment(alarm.getId(), savedComment);
Assert.assertNotNull(updatedAlarmComment);
Assert.assertEquals(newComment.get("text"), updatedAlarmComment.getComment().get("text"));
Assert.assertEquals("true", updatedAlarmComment.getComment().get("edited").asText());
Assert.assertNotNull(updatedAlarmComment.getComment().get("editedOn"));
testLogEntityAction(updatedAlarmComment, updatedAlarmComment.getId(), tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.UPDATED, 1);
testPushMsgToRuleEngineTime(updatedAlarmComment.getId(), tenantId, updatedAlarmComment, 1);
}
@Test
public void testUpdateAlarmViaTenant() throws Exception {
loginTenantAdmin();
AlarmComment savedComment = createAlarmComment(alarm.getId());
Mockito.reset(tbClusterService, auditLogService);
JsonNode newComment = JacksonUtil.newObjectNode().set("text", new TextNode("Updated comment"));
savedComment.setComment(newComment);
AlarmComment updatedAlarmComment = saveAlarmComment(alarm.getId(), savedComment);
Assert.assertNotNull(updatedAlarmComment);
Assert.assertEquals(newComment.get("text"), updatedAlarmComment.getComment().get("text"));
Assert.assertEquals("true", updatedAlarmComment.getComment().get("edited").asText());
Assert.assertNotNull(updatedAlarmComment.getComment().get("editedOn"));
testLogEntityAction(updatedAlarmComment, updatedAlarmComment.getId(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UPDATED, 1);
testPushMsgToRuleEngineTime(updatedAlarmComment.getId(), tenantId, updatedAlarmComment, 1);
}
@Test
public void testUpdateAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
AlarmComment savedComment = createAlarmComment(alarm.getId());
loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
JsonNode newComment = JacksonUtil.newObjectNode().set("text", new TextNode("Updated comment"));
savedComment.setComment(newComment);
doPost("/api/alarm/" + alarm.getId() + "/comment", savedComment)
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(savedComment.getId(), savedComment);
}
@Test
public void testUpdateAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
AlarmComment savedComment = createAlarmComment(alarm.getId());
loginDifferentCustomer();
Mockito.reset(tbClusterService, auditLogService);
JsonNode newComment = JacksonUtil.newObjectNode().set("text", new TextNode("Updated comment"));
savedComment.setComment(newComment);
doPost("/api/alarm/" + alarm.getId() + "/comment", savedComment)
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(savedComment.getId(), savedComment);
}
@Test
public void testDeleteAlarmViaCustomer() throws Exception {
loginCustomerUser();
AlarmComment alarmComment = createAlarmComment(alarm.getId());
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId() + "/comment/" + alarmComment.getId())
.andExpect(status().isOk());
testNotifyEntityOneTimeMsgToEdgeServiceNever(alarmComment, alarmComment.getId(), alarmComment.getId(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.DELETED);
}
@Test
public void testDeleteAlarmViaTenant() throws Exception {
loginTenantAdmin();
AlarmComment alarmComment = createAlarmComment(alarm.getId());
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId() + "/comment/" + alarmComment.getId())
.andExpect(status().isOk());
testNotifyEntityOneTimeMsgToEdgeServiceNever(alarmComment, alarmComment.getId(), alarmComment.getId(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED);
}
@Test
public void testDeleteAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
AlarmComment alarmComment = createAlarmComment(alarm.getId());
loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId() + "/comment/" + alarmComment.getId())
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(alarm.getId(), alarm);
}
@Test
public void testDeleteAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
AlarmComment alarmComment = createAlarmComment(alarm.getId());
loginDifferentCustomer();
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId() + "/comment/" + alarmComment.getId())
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
testNotifyEntityNever(alarm.getId(), alarm);
}
@Test
public void testFindAlarmCommentsViaCustomerUser() throws Exception {
loginCustomerUser();
List<AlarmComment> createdAlarmComments = new LinkedList<>();
final int size = 10;
for (int i = 0; i < size; i++) {
createdAlarmComments.add(
createAlarmComment(alarm.getId(), RandomStringUtils.randomAlphanumeric(10))
);
}
var response = doGetTyped(
"/api/alarm/" + alarm.getId() + "/comment?page=0&pageSize=" + size,
new TypeReference<PageData<AlarmCommentInfo>>() {}
);
var foundAlarmCommentInfos = response.getData();
Assert.assertNotNull("Found pageData is null", foundAlarmCommentInfos);
Assert.assertNotEquals(
"Expected alarms are not found!",
0, foundAlarmCommentInfos.size()
);
boolean allMatch = createdAlarmComments.stream()
.allMatch(alarmComment -> foundAlarmCommentInfos.stream()
.map(AlarmCommentInfo::getComment)
.anyMatch(comment -> alarmComment.getComment().equals(comment))
);
Assert.assertTrue("Created alarm comment doesn't match any found!", allMatch);
}
@Test
public void testFindAlarmsViaDifferentCustomerUser() throws Exception {
loginCustomerUser();
final int size = 10;
List<AlarmComment> createdAlarmComments = new LinkedList<>();
for (int i = 0; i < size; i++) {
createdAlarmComments.add(
createAlarmComment(alarm.getId(), RandomStringUtils.randomAlphanumeric(10))
);
}
loginDifferentCustomer();
doGet("/api/alarm/" + alarm.getId() + "/comment?page=0&pageSize=" + size)
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
}
@Test
public void testFindAlarmCommentsViaPublicCustomer() throws Exception {
loginTenantAdmin();
Device device = new Device();
device.setName("Test Public Device");
device.setLabel("Label");
device.setCustomerId(customerId);
device = doPost("/api/device", device, Device.class);
device = doPost("/api/customer/public/device/" + device.getUuidId(), Device.class);
String publicId = device.getCustomerId().toString();
Alarm alarm = Alarm.builder()
.originator(device.getId())
.status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type("Test")
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
Mockito.reset(tbClusterService, auditLogService);
AlarmComment alarmComment = createAlarmComment(alarm.getId());
resetTokens();
JsonNode publicLoginRequest = JacksonUtil.toJsonNode("{\"publicId\": \"" + publicId + "\"}");
JsonNode tokens = doPost("/api/auth/login/public", publicLoginRequest, JsonNode.class);
this.token = tokens.get("token").asText();
PageData<AlarmCommentInfo> pageData = doGetTyped(
"/api/alarm/" + alarm.getId() + "/comment" + "?page=0&pageSize=1", new TypeReference<PageData<AlarmCommentInfo>>() {}
);
Assert.assertNotNull("Found pageData is null", pageData);
Assert.assertNotEquals("Expected alarms are not found!", 0, pageData.getTotalElements());
AlarmCommentInfo alarmCommentInfo = pageData.getData().get(0);
boolean equals = alarmComment.getId().equals(alarmCommentInfo.getId()) && alarmComment.getComment().equals(alarmCommentInfo.getComment());
Assert.assertTrue("Created alarm doesn't match the found one!", equals);
}
private AlarmComment createAlarmComment(AlarmId alarmId, String text) {
AlarmComment alarmComment = AlarmComment.builder()
.comment(JacksonUtil.newObjectNode().set("text", new TextNode(text)))
.build();
return saveAlarmComment(alarmId, alarmComment);
}
private AlarmComment createAlarmComment(AlarmId alarmId) {
return createAlarmComment(alarmId, "Please take a look");
}
private AlarmComment saveAlarmComment(AlarmId alarmId, AlarmComment alarmComment) {
alarmComment = doPost("/api/alarm/" + alarmId + "/comment", alarmComment, AlarmComment.class);
Assert.assertNotNull(alarmComment);
return alarmComment;
}
}

23
application/src/test/java/org/thingsboard/server/controller/sql/AlarmCommentControllerSqlTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2022 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.controller.sql;
import org.thingsboard.server.controller.BaseAlarmCommentControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class AlarmCommentControllerSqlTest extends BaseAlarmCommentControllerTest {
}

3
application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -62,6 +63,8 @@ public class DefaultTbAlarmServiceTest {
@MockBean
protected AlarmService alarmService;
@MockBean
protected AlarmCommentService alarmCommentService;
@MockBean
protected AlarmSubscriptionService alarmSubscriptionService;
@MockBean
protected CustomerService customerService;

96
application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java

@ -0,0 +1,96 @@
/**
* Copyright © 2016-2022 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.alarmComment;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.dao.alarm.AlarmCommentOperationResult;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
import org.thingsboard.server.service.entitiy.alarm.DefaultTbAlarmCommentService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import java.util.UUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@Slf4j
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = DefaultTbAlarmCommentService.class)
@TestPropertySource(properties = {
"server.log_controller_error_stack_trace=false"
})
public class DefaultTbAlarmCommentServiceTest {
@MockBean
protected DbCallbackExecutorService dbExecutor;
@MockBean
protected TbNotificationEntityService notificationEntityService;
@MockBean
protected AlarmService alarmService;
@MockBean
protected AlarmCommentService alarmCommentService;
@MockBean
protected AlarmSubscriptionService alarmSubscriptionService;
@MockBean
protected CustomerService customerService;
@MockBean
protected TbClusterService tbClusterService;
@SpyBean
DefaultTbAlarmCommentService service;
@Test
public void testSave() throws ThingsboardException {
var alarm = new Alarm();
var alarmComment = new AlarmComment();
when(alarmCommentService.createOrUpdateAlarmComment(Mockito.any(), eq(alarmComment))).thenReturn(new AlarmCommentOperationResult(alarmComment, true));
service.saveAlarmComment(alarm, alarmComment, new User());
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarmComment(any(), any(), any(), any());
}
@Test
public void testDelete() {
var alarmId = new AlarmId(UUID.randomUUID());
var alarmCommentId = new AlarmCommentId(UUID.randomUUID());
when(alarmCommentService.deleteAlarmComment(Mockito.any(), eq(alarmCommentId))).thenReturn(new AlarmCommentOperationResult(new AlarmComment(), true));
service.deleteAlarmComment(new Alarm(alarmId), new AlarmComment(alarmCommentId), new User());
verify(notificationEntityService, times(1)).notifyDeleteAlarmComment(any(), any(), any());
}
}

36
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentOperationResult.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2022 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.alarm;
import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmComment;
@Data
public class AlarmCommentOperationResult {
private final AlarmComment alarmComment;
private final boolean successful;
private final boolean created;
public AlarmCommentOperationResult(AlarmComment alarmComment, boolean successful) {
this(alarmComment, successful, false);
}
public AlarmCommentOperationResult(AlarmComment alarmComment, boolean successful, boolean created) {
this.alarmComment = alarmComment;
this.successful = successful;
this.created = created;
}
}

38
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2022 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.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
public interface AlarmCommentService {
AlarmCommentOperationResult createOrUpdateAlarmComment(TenantId tenantId, AlarmComment alarmComment);
AlarmCommentOperationResult deleteAlarmComment(TenantId tenantId, AlarmCommentId alarmCommentId);
PageData<AlarmCommentInfo> findAlarmComments(TenantId tenantId, AlarmId alarmId, PageLink pageLink);
ListenableFuture<AlarmComment> findAlarmCommentByIdAsync(TenantId tenantId, AlarmCommentId alarmCommentId);
AlarmComment findAlarmCommentById(TenantId tenantId, AlarmCommentId alarmCommentId);
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -18,12 +18,12 @@ package org.thingsboard.server.dao.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
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.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;

2
common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java

@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
* @author Andrew Shvayka
*/
public enum EntityType {
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, ASSET_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC, QUEUE;
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, ALARM_COMMENT, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, ASSET_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC, QUEUE;
}

87
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java

@ -0,0 +1,87 @@
/**
* Copyright © 2016-2022 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.alarm;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel
@Data
@Builder
@AllArgsConstructor
public class AlarmComment extends BaseData<AlarmCommentId> implements HasName {
@ApiModelProperty(position = 3, value = "JSON object with Alarm id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private EntityId alarmId;
@ApiModelProperty(position = 4, value = "JSON object with User id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private UserId userId;
@ApiModelProperty(position = 5, value = "Defines origination of comment", example = "System/Other", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@NoXss
@Length(fieldName = "type")
private String type;
@ApiModelProperty(position = 6, value = "JSON object with text of comment.", dataType = "com.fasterxml.jackson.databind.JsonNode")
private transient JsonNode comment;
@ApiModelProperty(position = 1, value = "JSON object with the alarm comment Id. " +
"Specify this field to update the alarm comment. " +
"Referencing non-existing alarm Id will cause error. " +
"Omit this field to create new alarm." )
@Override
public AlarmCommentId getId() {
return super.getId();
}
@ApiModelProperty(position = 2, value = "Timestamp of the alarm comment creation, in milliseconds", example = "1634058704567", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override
public long getCreatedTime() {
return super.getCreatedTime();
}
public AlarmComment() {
super();
}
public AlarmComment(AlarmCommentId id) {
super(id);
}
@Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@ApiModelProperty(position = 5, required = true, value = "representing comment text", example = "Please take a look")
public String getName() {
return comment.toString();
}
public AlarmComment(AlarmComment alarmComment) {
super(alarmComment.getId());
this.createdTime = alarmComment.getCreatedTime();
this.alarmId = alarmComment.getAlarmId();
this.type = alarmComment.getType();
this.comment = alarmComment.getComment();
this.userId = alarmComment.getUserId();
}
}

52
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentInfo.java

@ -0,0 +1,52 @@
/**
* Copyright © 2016-2022 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.alarm;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ApiModel
@Data
@EqualsAndHashCode(callSuper = true)
public class AlarmCommentInfo extends AlarmComment {
private static final long serialVersionUID = 2807343093519543377L;
@ApiModelProperty(position = 19, value = "User first name", example = "John")
private String firstName;
@ApiModelProperty(position = 19, value = "User last name", example = "Brown")
private String lastName;
@ApiModelProperty(position = 19, value = "User email address", example = "johnBrown@gmail.com")
private String email;
public AlarmCommentInfo() {
super();
}
public AlarmCommentInfo(AlarmComment alarmComment) {
super(alarmComment);
}
public AlarmCommentInfo(AlarmComment alarmComment, String firstName, String lastName, String email) {
super(alarmComment);
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
}

45
common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmCommentId.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2022 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.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
@ApiModel
public class AlarmCommentId extends UUIDBased implements EntityId{
private static final long serialVersionUID = 1L;
@JsonCreator
public AlarmCommentId(@JsonProperty("id") UUID id) {
super(id);
}
public static AlarmCommentId fromString(String commentId) {
return new AlarmCommentId(UUID.fromString(commentId));
}
@ApiModelProperty(position = 2, required = true, value = "string", example = "ALARM_COMMENT", allowableValues = "ALARM_COMMENT")
@Override
public EntityType getEntityType() {
return EntityType.ALARM_COMMENT;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -53,6 +53,8 @@ public class EntityIdFactory {
return new AssetId(uuid);
case ALARM:
return new AlarmId(uuid);
case ALARM_COMMENT:
return new AlarmCommentId(uuid);
case RULE_CHAIN:
return new RuleChainId(uuid);
case RULE_NODE:

43
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentDao.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2022 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.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
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;
import java.util.UUID;
public interface AlarmCommentDao extends Dao<AlarmComment> {
AlarmComment createAlarmComment(TenantId tenantId, AlarmComment alarmComment);
void deleteAlarmComment(TenantId tenantId, AlarmCommentId alarmCommentId);
AlarmComment findAlarmCommentById(TenantId tenantId, UUID key);
PageData<AlarmCommentInfo> findAlarmComments(TenantId tenantId, AlarmId id, PageLink pageLink);
ListenableFuture<AlarmComment> findAlarmCommentByIdAsync(TenantId tenantId, UUID key);
void cleanUpAlarmComments(long auditLogsExpTime);
}

127
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java

@ -0,0 +1,127 @@
/**
* Copyright © 2016-2022 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.alarm;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
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.entity.AbstractEntityService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.user.UserService;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@Slf4j
public class BaseAlarmCommentService extends AbstractEntityService implements AlarmCommentService{
@Autowired
private AlarmCommentDao alarmCommentDao;
@Autowired
private UserService userService;
@Autowired
private DataValidator<AlarmComment> alarmCommentDataValidator;
@Override
public AlarmCommentOperationResult createOrUpdateAlarmComment(TenantId tenantId, AlarmComment alarmComment) {
alarmCommentDataValidator.validate(alarmComment, tenantId);
if (alarmComment.getId() == null) {
return createAlarmComment(tenantId, alarmComment);
} else {
return updateAlarmComment(tenantId, alarmComment);
}
}
@Override
public AlarmCommentOperationResult deleteAlarmComment(TenantId tenantId, AlarmCommentId alarmCommentId) {
log.debug("Deleting Alarm Comment with id: {}", alarmCommentId);
AlarmCommentOperationResult result = new AlarmCommentOperationResult(new AlarmComment(), true);
alarmCommentDao.deleteAlarmComment(tenantId, alarmCommentId);
return result;
}
@Override
public PageData<AlarmCommentInfo> findAlarmComments(TenantId tenantId, AlarmId alarmId, PageLink pageLink) {
log.trace("Executing findAlarmComments by alarmId [{}]", alarmId);
return alarmCommentDao.findAlarmComments(tenantId, alarmId, pageLink);
}
@Override
public ListenableFuture<AlarmComment> findAlarmCommentByIdAsync(TenantId tenantId, AlarmCommentId alarmCommentId) {
log.trace("Executing findAlarmCommentByIdAsync by alarmCommentId [{}]", alarmCommentId);
validateId(alarmCommentId, "Incorrect alarmCommentId " + alarmCommentId);
return alarmCommentDao.findAlarmCommentByIdAsync(tenantId, alarmCommentId.getId());
}
@Override
public AlarmComment findAlarmCommentById(TenantId tenantId, AlarmCommentId alarmCommentId) {
log.trace("Executing findAlarmCommentByIdAsync by alarmCommentId [{}]", alarmCommentId);
validateId(alarmCommentId, "Incorrect alarmCommentId " + alarmCommentId);
return alarmCommentDao.findById(tenantId, alarmCommentId.getId());
}
private AlarmCommentOperationResult createAlarmComment(TenantId tenantId, AlarmComment alarmComment) {
log.debug("New Alarm comment : {}", alarmComment);
if (alarmComment.getType() == null) {
alarmComment.setType("OTHER");
}
if (alarmComment.getId() == null) {
UUID uuid = Uuids.timeBased();
alarmComment.setId(new AlarmCommentId(uuid));
alarmComment.setCreatedTime(Uuids.unixTimestamp(uuid));
}
AlarmComment saved = alarmCommentDao.createAlarmComment(tenantId, alarmComment);
return new AlarmCommentOperationResult(saved, true, true);
}
private AlarmCommentOperationResult updateAlarmComment(TenantId tenantId, AlarmComment newAlarmComment) {
log.debug("Update Alarm comment : {}", newAlarmComment);
AlarmComment existing = alarmCommentDao.findAlarmCommentById(tenantId, newAlarmComment.getId().getId());
if (existing != null) {
if (newAlarmComment.getComment() != null) {
JsonNode comment = newAlarmComment.getComment();
UUID uuid = Uuids.timeBased();
((ObjectNode) comment).put("edited", "true");
((ObjectNode) comment).put("editedOn", Uuids.unixTimestamp(uuid));
existing.setComment(comment);
}
AlarmComment result = alarmCommentDao.save(tenantId, existing);
return new AlarmCommentOperationResult(result, true, true);
}
return null;
}
}

8
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -297,6 +297,14 @@ public class ModelConstants {
public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id";
public static final String ALARM_COMMENT_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ALARM_COMMENT_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String ALARM_COMMENT_COLUMN_FAMILY_NAME = "alarm_comment";
public static final String ALARM_COMMENT_ALARM_ID = "alarm_id";
public static final String ALARM_COMMENT_USER_ID = USER_ID_PROPERTY;
public static final String ALARM_COMMENT_TYPE = "type";
public static final String ALARM_COMMENT_COMMENT = "comment";
/**
* Cassandra entity relation constants.
*/

99
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java

@ -0,0 +1,99 @@
/**
* Copyright © 2016-2022 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.model.sql;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.model.BaseEntity;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COMMENT_ALARM_ID;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COMMENT_COMMENT;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COMMENT_TYPE;
@Data
@EqualsAndHashCode(callSuper = true)
@TypeDef(name = "json", typeClass = JsonStringType.class)
@MappedSuperclass
public abstract class AbstractAlarmCommentEntity<T extends AlarmComment> extends BaseSqlEntity<T> implements BaseEntity<T> {
@Column(name = ALARM_COMMENT_ALARM_ID, columnDefinition = "uuid")
private UUID alarmId;
@Column(name = ModelConstants.ALARM_COMMENT_USER_ID)
private UUID userId;
@Column(name = ALARM_COMMENT_TYPE)
private String type;
@Type(type = "json")
@Column(name = ALARM_COMMENT_COMMENT)
private JsonNode comment;
public AbstractAlarmCommentEntity() {
super();
}
public AbstractAlarmCommentEntity(AlarmComment alarmComment) {
if (alarmComment.getId() != null) {
this.setUuid(alarmComment.getUuidId());
}
this.setCreatedTime(alarmComment.getCreatedTime());
if (alarmComment.getAlarmId() != null) {
this.alarmId = alarmComment.getAlarmId().getId();
}
if (alarmComment.getUserId() != null) {
this.userId = alarmComment.getUserId().getId();
}
if (alarmComment.getType() != null) {
this.type = alarmComment.getType();
}
this.setComment(alarmComment.getComment());
}
public AbstractAlarmCommentEntity(AlarmCommentEntity alarmCommentEntity) {
this.setId(alarmCommentEntity.getId());
this.setCreatedTime(alarmCommentEntity.getCreatedTime());
this.userId = alarmCommentEntity.getUserId();
this.alarmId = alarmCommentEntity.getAlarmId();
this.type = alarmCommentEntity.getType();
this.comment = alarmCommentEntity.getComment();
}
protected AlarmComment toAlarmComment() {
AlarmComment alarmComment = new AlarmComment(new AlarmCommentId(id));
alarmComment.setCreatedTime(createdTime);
alarmComment.setAlarmId(new AlarmId(alarmId));
if (userId != null) {
alarmComment.setUserId(new UserId(userId));
}
alarmComment.setType(type);
alarmComment.setComment(comment);
return alarmComment;
}
}

55
dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentEntity.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2022 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.model.sql;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Entity;
import javax.persistence.Table;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COMMENT_COLUMN_FAMILY_NAME;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ALARM_COMMENT_COLUMN_FAMILY_NAME)
public class AlarmCommentEntity extends AbstractAlarmCommentEntity<AlarmComment> {
public AlarmCommentEntity() {
super();
}
public AlarmCommentEntity(AlarmCommentInfo alarmCommentInfo) {
super(alarmCommentInfo);
}
public AlarmCommentEntity(AlarmComment alarmComment) {
super(alarmComment);
}
@Override
public AlarmComment toData() {
return super.toAlarmComment();
}
}

53
dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentInfoEntity.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2022 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.model.sql;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import java.util.HashMap;
import java.util.Map;
@Data
@EqualsAndHashCode(callSuper = true)
public class AlarmCommentInfoEntity extends AbstractAlarmCommentEntity<AlarmCommentInfo> {
private String firstName;
private String lastName;
private String email;
public AlarmCommentInfoEntity() {
super();
}
public AlarmCommentInfoEntity(AlarmCommentEntity alarmCommentEntity) {
super(alarmCommentEntity);
}
public AlarmCommentInfoEntity(AlarmCommentEntity alarmCommentEntity, String firstName, String lastName, String email) {
super(alarmCommentEntity);
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
@Override
public AlarmCommentInfo toData() {
return new AlarmCommentInfo(super.toAlarmComment(), this.firstName, this.lastName, this.email);
}
}

23
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -67,6 +67,29 @@ public abstract class DataValidator<D extends BaseData<?>> {
}
}
public D validate(D data, TenantId tenantId) {
try {
if (data == null) {
throw new DataValidationException("Data object can't be null!");
}
ConstraintValidator.validateFields(data);
validateDataImpl(tenantId, data);
D old;
if (data.getId() == null) {
validateCreate(tenantId, data);
old = null;
} else {
old = validateUpdate(tenantId, data);
}
return old;
} catch (DataValidationException e) {
log.error("{} object is invalid: [{}]", data == null ? "Data" : data.getClass().getSimpleName(), e.getMessage());
throw e;
}
}
protected void validateDataImpl(TenantId tenantId, D data) {
}

38
dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmCommentDataValidator.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2022 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.service.validator;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
@Component
@AllArgsConstructor
public class AlarmCommentDataValidator extends DataValidator<AlarmComment> {
@Override
protected void validateDataImpl(TenantId tenantId, AlarmComment alarmComment) {
if (alarmComment.getComment() == null) {
throw new DataValidationException("Alarm comment should be specified!");
}
if (alarmComment.getAlarmId() == null) {
throw new DataValidationException("Alarm id should be specified!");
}
}
}

39
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmCommentRepository.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2022 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.sql.alarm;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.model.sql.AlarmCommentEntity;
import org.thingsboard.server.dao.model.sql.AlarmCommentInfoEntity;
import java.util.UUID;
public interface AlarmCommentRepository extends JpaRepository<AlarmCommentEntity, UUID> {
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmCommentInfoEntity(a, u.firstName, u.lastName, u.email) FROM AlarmCommentEntity a " +
"LEFT JOIN UserEntity u on u.id = a.userId " +
"WHERE a.alarmId = :alarmId ",
countQuery = "" +
"SELECT count(a) " +
"FROM AlarmCommentEntity a " +
"WHERE a.alarmId = :alarmId ")
Page<AlarmCommentInfoEntity> findAllByAlarmId(@Param("alarmId") UUID alarmId,
Pageable pageable);
}

106
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDao.java

@ -0,0 +1,106 @@
/**
* Copyright © 2016-2022 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.sql.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
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.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmCommentDao;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.sql.AlarmCommentEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COMMENT_COLUMN_FAMILY_NAME;
@Slf4j
@Component
@SqlDao
@RequiredArgsConstructor
public class JpaAlarmCommentDao extends JpaAbstractDao<AlarmCommentEntity, AlarmComment> implements AlarmCommentDao {
private final SqlPartitioningRepository partitioningRepository;
@Value("${sql.alarm_comments.partition_size:168}")
private int partitionSizeInHours;
@Autowired
private AlarmCommentRepository alarmCommentRepository;
private static final String TABLE_NAME = ALARM_COMMENT_COLUMN_FAMILY_NAME;
@Override
public AlarmComment createAlarmComment(TenantId tenantId, AlarmComment alarmComment){
log.trace("Saving entity {}", alarmComment);
partitioningRepository.createPartitionIfNotExists(ALARM_COMMENT_COLUMN_FAMILY_NAME, alarmComment.getCreatedTime(), TimeUnit.HOURS.toMillis(partitionSizeInHours));
AlarmCommentEntity saved = alarmCommentRepository.save(new AlarmCommentEntity(alarmComment));
return DaoUtil.getData(saved);
}
@Override
public void deleteAlarmComment(TenantId tenantId, AlarmCommentId alarmCommentId){
log.trace("Try to delete entity alarm comment by id using [{}]", alarmCommentId);
alarmCommentRepository.deleteById(alarmCommentId.getId());
}
@Override
public PageData<AlarmCommentInfo> findAlarmComments(TenantId tenantId, AlarmId id, PageLink pageLink){
log.trace("Try to find alarm comments by alarm id using [{}]", id);
return DaoUtil.toPageData(
alarmCommentRepository.findAllByAlarmId(id.getId(), DaoUtil.toPageable(pageLink)));
}
@Override
public AlarmComment findAlarmCommentById(TenantId tenantId, UUID key) {
log.trace("Try to find alarm comment by id using [{}]", key);
return DaoUtil.getData(alarmCommentRepository.findById(key));
}
@Override
public ListenableFuture<AlarmComment> findAlarmCommentByIdAsync(TenantId tenantId, UUID key) {
log.trace("Try to find alarm comment by id using [{}]", key);
return findByIdAsync(tenantId, key);
}
@Override
public void cleanUpAlarmComments(long expTime) {
partitioningRepository.dropPartitionsBefore(TABLE_NAME, expTime, TimeUnit.HOURS.toMillis(partitionSizeInHours));
}
@Override
protected Class<AlarmCommentEntity> getEntityClass() {
return AlarmCommentEntity.class;
}
@Override
protected JpaRepository<AlarmCommentEntity, UUID> getRepository() {
return alarmCommentRepository;
}
}

2
dao/src/main/resources/sql/schema-entities-idx.sql

@ -79,3 +79,5 @@ CREATE INDEX IF NOT EXISTS idx_rule_node_external_id ON rule_node(rule_chain_id,
CREATE INDEX IF NOT EXISTS idx_rule_node_type ON rule_node(type);
CREATE INDEX IF NOT EXISTS idx_api_usage_state_entity_id ON api_usage_state(entity_id);
CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id ASC);

10
dao/src/main/resources/sql/schema-entities.sql

@ -776,3 +776,13 @@ CREATE TABLE IF NOT EXISTS user_auth_settings (
user_id uuid UNIQUE NOT NULL CONSTRAINT fk_user_auth_settings_user_id REFERENCES tb_user(id),
two_fa_settings varchar
);
CREATE TABLE IF NOT EXISTS alarm_comment (
id uuid NOT NULL,
created_time bigint NOT NULL,
alarm_id uuid NOT NULL,
user_id uuid,
type varchar(255) NOT NULL,
comment varchar(1000000),
CONSTRAINT fk_alarm_comment_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE
) PARTITION BY RANGE (created_time);

3
dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java

@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
@ -149,6 +150,8 @@ public abstract class AbstractServiceTest {
@Autowired
protected AlarmService alarmService;
@Autowired
protected AlarmCommentService alarmCommentService;
@Autowired
protected RuleChainService ruleChainService;

162
dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java

@ -0,0 +1,162 @@
/**
* Copyright © 2016-2022 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.service;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.TenantId;
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 java.util.UUID;
import java.util.concurrent.ExecutionException;
public abstract class BaseAlarmCommentServiceTest extends AbstractServiceTest {
public static final String TEST_ALARM = "TEST_ALARM";
private TenantId tenantId;
private Alarm alarm;
private User user;
@Before
public void before() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId();
alarm = Alarm.builder().tenantId(tenantId).originator(new AssetId(Uuids.timeBased()))
.type(TEST_ALARM)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(System.currentTimeMillis()).build();
alarm = alarmService.createOrUpdateAlarm(alarm).getAlarm();
user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(tenantId);
user.setEmail("tenant@thingsboard.org");
user.setFirstName("John");
user.setLastName("Brown");
user = userService.saveUser(user);
}
@After
public void after() {
alarmService.deleteAlarm(tenantId, alarm.getId());
tenantService.deleteTenant(tenantId);
}
@Test
public void testSaveAndFetchAlarmComment() throws ExecutionException, InterruptedException {
AlarmComment alarmComment = AlarmComment.builder().alarmId(alarm.getId())
.userId(user.getId())
.type("OTHER")
.comment(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10)))
.build();
AlarmComment createdComment = alarmCommentService.createOrUpdateAlarmComment(tenantId, alarmComment).getAlarmComment();
Assert.assertNotNull(createdComment);
Assert.assertNotNull(createdComment.getId());
Assert.assertEquals(alarm.getId(), createdComment.getAlarmId());
Assert.assertEquals(user.getId(), createdComment.getUserId());
Assert.assertEquals("OTHER", createdComment.getType());
Assert.assertTrue(createdComment.getCreatedTime() > 0);
AlarmComment fetched = alarmCommentService.findAlarmCommentByIdAsync(tenantId, createdComment.getId()).get();
Assert.assertEquals(createdComment, fetched);
PageData<AlarmCommentInfo> alarmComments = alarmCommentService.findAlarmComments(tenantId, alarm.getId(), new PageLink(10, 0));
Assert.assertNotNull(alarmComments.getData());
Assert.assertEquals(1, alarmComments.getData().size());
Assert.assertEquals(createdComment, new AlarmComment(alarmComments.getData().get(0)));
}
@Test
public void testUpdateAlarmComment() throws ExecutionException, InterruptedException {
UserId userId = new UserId(UUID.randomUUID());
AlarmComment alarmComment = AlarmComment.builder().alarmId(alarm.getId())
.userId(userId)
.type("OTHER")
.comment(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10)))
.build();
AlarmComment createdComment = alarmCommentService.createOrUpdateAlarmComment(tenantId, alarmComment).getAlarmComment();
Assert.assertNotNull(createdComment);
Assert.assertNotNull(createdComment.getId());
//update comment
String newComment = "new comment";
createdComment.setComment(JacksonUtil.newObjectNode().put("text", newComment));
AlarmComment updatedComment = alarmCommentService.createOrUpdateAlarmComment(tenantId, createdComment).getAlarmComment();
Assert.assertEquals(alarm.getId(), updatedComment.getAlarmId());
Assert.assertEquals(userId, updatedComment.getUserId());
Assert.assertEquals("OTHER", updatedComment.getType());
Assert.assertTrue(updatedComment.getCreatedTime() > 0);
Assert.assertEquals(newComment, updatedComment.getComment().get("text").asText());
Assert.assertEquals("true", updatedComment.getComment().get("edited").asText());
Assert.assertNotNull(updatedComment.getComment().get("editedOn").asText());
AlarmComment fetched = alarmCommentService.findAlarmCommentByIdAsync(tenantId, createdComment.getId()).get();
Assert.assertEquals(updatedComment, fetched);
PageData<AlarmCommentInfo> alarmComments = alarmCommentService.findAlarmComments(tenantId, alarm.getId(), new PageLink(10, 0));
Assert.assertNotNull(alarmComments.getData());
Assert.assertEquals(1, alarmComments.getData().size());
Assert.assertEquals(updatedComment, new AlarmComment(alarmComments.getData().get(0)));
}
@Test
public void testDeleteAlarmComment() throws ExecutionException, InterruptedException {
UserId userId = new UserId(UUID.randomUUID());
AlarmComment alarmComment = AlarmComment.builder().alarmId(alarm.getId())
.userId(userId)
.type("OTHER")
.comment(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10)))
.build();
AlarmComment createdComment = alarmCommentService.createOrUpdateAlarmComment(tenantId, alarmComment).getAlarmComment();
Assert.assertNotNull(createdComment);
Assert.assertNotNull(createdComment.getId());
Assert.assertTrue("Alarm comment was not deleted when expected", alarmCommentService.deleteAlarmComment(tenantId, createdComment.getId()).isSuccessful());
AlarmComment fetched = alarmCommentService.findAlarmCommentByIdAsync(tenantId, createdComment.getId()).get();
Assert.assertNull("Alarm comment was returned when it was expected to be null", fetched);
}
}

23
dao/src/test/java/org/thingsboard/server/dao/service/sql/AlarmCommentServiceSqlTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2022 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.service.sql;
import org.thingsboard.server.dao.service.BaseAlarmCommentServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class AlarmCommentServiceSqlTest extends BaseAlarmCommentServiceTest {
}

90
dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java

@ -0,0 +1,90 @@
/**
* Copyright © 2016-2022 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.sql.alarm;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.AbstractJpaDaoTest;
import org.thingsboard.server.dao.alarm.AlarmCommentDao;
import org.thingsboard.server.dao.alarm.AlarmDao;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
@Slf4j
public class JpaAlarmCommentDaoTest extends AbstractJpaDaoTest {
@Autowired
private AlarmCommentDao alarmCommentDao;
@Autowired
private AlarmDao alarmDao;
@Test
public void testFindAlarmCommentsByAlarmId() {
log.info("Current system time in millis = {}", System.currentTimeMillis());
UUID tenantId = UUID.randomUUID();
UUID userId = UUID.randomUUID();
UUID alarmId1 = UUID.randomUUID();
UUID alarmId2 = UUID.randomUUID();
UUID commentId1 = UUID.randomUUID();
UUID commentId2 = UUID.randomUUID();
UUID commentId3 = UUID.randomUUID();
saveAlarm(alarmId1, UUID.randomUUID(), UUID.randomUUID(), "TEST_ALARM");
saveAlarm(alarmId2, UUID.randomUUID(), UUID.randomUUID(), "TEST_ALARM");
saveAlarmComment(commentId1, alarmId1, userId, "OTHER");
saveAlarmComment(commentId2, alarmId1, userId, "OTHER");
saveAlarmComment(commentId3, alarmId2, userId, "OTHER");
int count = alarmCommentDao.findAlarmComments(TenantId.fromUUID(tenantId), new AlarmId(alarmId1), new PageLink(10, 0)).getData().size();
assertEquals(2, count);
}
private void saveAlarm(UUID id, UUID tenantId, UUID deviceId, String type) {
Alarm alarm = new Alarm();
alarm.setId(new AlarmId(id));
alarm.setTenantId(TenantId.fromUUID(tenantId));
alarm.setOriginator(new DeviceId(deviceId));
alarm.setType(type);
alarm.setPropagate(true);
alarm.setStartTs(System.currentTimeMillis());
alarm.setEndTs(System.currentTimeMillis());
alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
alarmDao.save(TenantId.fromUUID(tenantId), alarm);
}
private void saveAlarmComment(UUID id, UUID alarmId, UUID userId, String type) {
AlarmComment alarmComment = new AlarmComment();
alarmComment.setId(new AlarmCommentId(id));
alarmComment.setAlarmId(TenantId.fromUUID(alarmId));
alarmComment.setUserId(new UserId(userId));
alarmComment.setType(type);
alarmComment.setComment(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10)));
alarmCommentDao.createAlarmComment(TenantId.fromUUID(UUID.randomUUID()), alarmComment);
}
}

3
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java

@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.rule.RuleNodeState;
import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
@ -237,6 +238,8 @@ public interface TbContext {
RuleEngineAlarmService getAlarmService();
AlarmCommentService getAlarmCommentService();
RuleChainService getRuleChainService();
RuleEngineRpcService getRpcService();

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java

@ -22,6 +22,7 @@ import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityFieldsData;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
@ -56,6 +57,9 @@ public class EntitiesFieldsAsyncLoader {
case ALARM:
return getAsync(ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), (AlarmId) original),
EntityFieldsData::new);
case ALARM_COMMENT:
return getAsync(ctx.getAlarmCommentService().findAlarmCommentByIdAsync(ctx.getTenantId(), (AlarmCommentId) original),
EntityFieldsData::new);
case RULE_CHAIN:
return getAsync(ctx.getRuleChainService().findRuleChainByIdAsync(ctx.getTenantId(), (RuleChainId) original),
EntityFieldsData::new);

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java

@ -18,6 +18,8 @@ package org.thingsboard.rule.engine.util;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.ApiUsageStateId;
import org.thingsboard.server.common.data.id.AssetId;
@ -69,6 +71,10 @@ public class TenantIdLoader {
case ALARM:
tenantEntity = ctx.getAlarmService().findAlarmById(ctxTenantId, new AlarmId(id));
break;
case ALARM_COMMENT:
AlarmComment alarmComment = ctx.getAlarmCommentService().findAlarmCommentById(ctxTenantId, new AlarmCommentId(id));
tenantEntity = ctx.getAlarmService().findAlarmById(ctxTenantId, new AlarmId(alarmComment.getAlarmId().getId()));
break;
case RULE_CHAIN:
tenantEntity = ctx.getRuleChainService().findRuleChainById(ctxTenantId, new RuleChainId(id));
break;

13
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java

@ -41,9 +41,11 @@ import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
@ -56,6 +58,7 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
@ -93,6 +96,8 @@ public class TenantIdLoaderTest {
@Mock
private RuleEngineAlarmService alarmService;
@Mock
private AlarmCommentService alarmCommentService;
@Mock
private RuleChainService ruleChainService;
@Mock
private EntityViewService entityViewService;
@ -190,6 +195,14 @@ public class TenantIdLoaderTest {
when(ctx.getAlarmService()).thenReturn(alarmService);
doReturn(alarm).when(alarmService).findAlarmById(eq(tenantId), any());
break;
case ALARM_COMMENT:
AlarmComment alarmComment = new AlarmComment();
alarmComment.setAlarmId(new AlarmId(UUID.randomUUID()));
when(ctx.getAlarmCommentService()).thenReturn(alarmCommentService);
doReturn(alarmComment).when(alarmCommentService).findAlarmCommentById(eq(tenantId), any());
break;
case RULE_CHAIN:
RuleChain ruleChain = new RuleChain();

Loading…
Cancel
Save