Browse Source

Merge remote-tracking branch 'upstream/develop/3.5' into feature-alarm-assigning

pull/8090/head
zbeacon 4 years ago
parent
commit
5494bc7d2b
  1. 2
      application/pom.xml
  2. 13
      application/src/main/data/upgrade/3.4.3/schema_update.sql
  3. 5
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  4. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  5. 127
      application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java
  6. 20
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  7. 3
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  8. 4
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  9. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java
  10. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
  11. 6
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  12. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java
  13. 52
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java
  14. 18
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  15. 27
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java
  16. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java
  17. 18
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  18. 2
      application/src/main/resources/thingsboard.yml
  19. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  20. 363
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
  21. 14
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  22. 23
      application/src/test/java/org/thingsboard/server/controller/sql/AlarmCommentControllerSqlTest.java
  23. 12
      application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
  24. 96
      application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java
  25. 2
      common/actor/pom.xml
  26. 2
      common/cache/pom.xml
  27. 2
      common/cluster-api/pom.xml
  28. 2
      common/coap-server/pom.xml
  29. 2
      common/dao-api/pom.xml
  30. 38
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java
  31. 8
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
  32. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  33. 2
      common/data/pom.xml
  34. 87
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java
  35. 52
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentInfo.java
  36. 22
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentType.java
  37. 5
      common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
  38. 39
      common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmCommentId.java
  39. 2
      common/edge-api/pom.xml
  40. 2
      common/message/pom.xml
  41. 2
      common/pom.xml
  42. 2
      common/queue/pom.xml
  43. 2
      common/script/pom.xml
  44. 2
      common/script/remote-js-client/pom.xml
  45. 2
      common/script/script-api/pom.xml
  46. 2
      common/stats/pom.xml
  47. 2
      common/transport/coap/pom.xml
  48. 2
      common/transport/http/pom.xml
  49. 2
      common/transport/lwm2m/pom.xml
  50. 2
      common/transport/mqtt/pom.xml
  51. 2
      common/transport/pom.xml
  52. 2
      common/transport/snmp/pom.xml
  53. 2
      common/transport/transport-api/pom.xml
  54. 2
      common/util/pom.xml
  55. 2
      common/version-control/pom.xml
  56. 2
      dao/pom.xml
  57. 41
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentDao.java
  58. 115
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java
  59. 3
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  60. 7
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  61. 8
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  62. 98
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java
  63. 55
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentEntity.java
  64. 50
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentInfoEntity.java
  65. 15
      dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java
  66. 38
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmCommentDataValidator.java
  67. 39
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmCommentRepository.java
  68. 99
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDao.java
  69. 2
      dao/src/main/resources/sql/schema-entities-idx.sql
  70. 12
      dao/src/main/resources/sql/schema-entities.sql
  71. 3
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  72. 164
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java
  73. 23
      dao/src/test/java/org/thingsboard/server/dao/service/sql/AlarmCommentServiceSqlTest.java
  74. 91
      dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java
  75. 2
      msa/black-box-tests/pom.xml
  76. 2
      msa/js-executor/package.json
  77. 2
      msa/js-executor/pom.xml
  78. 2
      msa/pom.xml
  79. 2
      msa/tb-node/pom.xml
  80. 2
      msa/tb/pom.xml
  81. 2
      msa/transport/coap/pom.xml
  82. 2
      msa/transport/http/pom.xml
  83. 2
      msa/transport/lwm2m/pom.xml
  84. 2
      msa/transport/mqtt/pom.xml
  85. 2
      msa/transport/pom.xml
  86. 2
      msa/transport/snmp/pom.xml
  87. 2
      msa/vc-executor-docker/pom.xml
  88. 2
      msa/vc-executor/pom.xml
  89. 2
      msa/web-ui/package.json
  90. 2
      msa/web-ui/pom.xml
  91. 4
      netty-mqtt/pom.xml
  92. 2
      pom.xml
  93. 2
      rest-client/pom.xml
  94. 2
      rule-engine/pom.xml
  95. 2
      rule-engine/rule-engine-api/pom.xml
  96. 3
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  97. 2
      rule-engine/rule-engine-components/pom.xml
  98. 3
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java
  99. 2
      tools/pom.xml
  100. 2
      transport/coap/pom.xml

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>application</artifactId> <artifactId>application</artifactId>

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

@ -22,4 +22,15 @@ ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assignee_id UUID;
ALTER TABLE entity_alarm ADD COLUMN IF NOT EXISTS assignee_id UUID; ALTER TABLE entity_alarm ADD COLUMN IF NOT EXISTS assignee_id UUID;
-- ALARM ASSIGN TO USER END -- ALARM ASSIGN TO USER END
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(10000),
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.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.stats.TbApiUsageReportClient; 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.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
@ -278,6 +279,10 @@ public class ActorSystemContext {
@Getter @Getter
private AlarmSubscriptionService alarmService; private AlarmSubscriptionService alarmService;
@Autowired
@Getter
private AlarmCommentService alarmCommentService;
@Autowired @Autowired
@Getter @Getter
private JsInvokeService jsInvokeService; 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.TbMsgProcessingStackItem;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 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.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
@ -591,6 +592,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getAlarmService(); return mainCtx.getAlarmService();
} }
@Override
public AlarmCommentService getAlarmCommentService() {
return mainCtx.getAlarmCommentService();
}
@Override @Override
public RuleChainService getRuleChainService() { public RuleChainService getRuleChainService() {
return mainCtx.getRuleChainService(); return mainCtx.getRuleChainService();

127
application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.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.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 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.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 void 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, alarmId);
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));
}
}

20
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.TenantProfile;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; 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.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo; 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.edge.EdgeInfo;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; 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.AlarmId;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId; 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.rule.RuleNode;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle; 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.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
@ -200,6 +203,9 @@ public abstract class BaseController {
@Autowired @Autowired
protected AlarmSubscriptionService alarmService; protected AlarmSubscriptionService alarmService;
@Autowired
protected AlarmCommentService alarmCommentService;
@Autowired @Autowired
protected DeviceCredentialsService deviceCredentialsService; protected DeviceCredentialsService deviceCredentialsService;
@ -759,6 +765,20 @@ public abstract class BaseController {
} }
} }
AlarmComment checkAlarmCommentId(AlarmCommentId alarmCommentId, AlarmId alarmId) 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");
if (!alarmId.equals(alarmComment.getAlarmId())) {
throw new ThingsboardException("Alarm id does not match with comment alarm id", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
return alarmComment;
} catch (Exception e) {
throw handleException(e, false);
}
}
WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, Operation operation) throws ThingsboardException { WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, Operation operation) throws ThingsboardException {
return checkWidgetsBundleId(widgetsBundleId, operation, getCurrentUser()); return checkWidgetsBundleId(widgetsBundleId, operation, getCurrentUser());
} }

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

@ -46,6 +46,8 @@ public class ControllerConstants {
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 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_ID_PARAM_DESCRIPTION = "A string value representing the alarm id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ASSIGN_ID_PARAM_DESCRIPTION = "A string value representing the user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String ASSIGN_ID_PARAM_DESCRIPTION = "A string value representing the user 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 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 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'"; protected static final String ENTITY_TYPE_PARAM_DESCRIPTION = "A string value representing the entity type. For example, 'DEVICE'";
@ -102,6 +104,7 @@ public class ControllerConstants {
protected static final String ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, description, isDefault"; 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 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_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 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 EDGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle";
protected static final String RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, root"; protected static final String RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, root";

4
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -233,6 +233,10 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.4.1 to 3.4.2 ..."); log.info("Upgrading ThingsBoard from version 3.4.1 to 3.4.2 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.4.1"); databaseEntitiesUpgradeService.upgradeDatabase("3.4.1");
dataUpdateService.updateData("3.4.1"); dataUpdateService.updateData("3.4.1");
break;
case "3.4.3":
log.info("Upgrading ThingsBoard from version 3.4.3 to 3.5 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.4.3");
log.info("Updating system data..."); log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.updateSystemWidgets();
break; break;

12
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java

@ -15,8 +15,10 @@
*/ */
package org.thingsboard.server.service.edge.rpc.fetch; package org.thingsboard.server.service.edge.rpc.fetch;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
@ -28,6 +30,8 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import static org.thingsboard.server.service.edge.DefaultEdgeNotificationService.EDGE_IS_ROOT_BODY_KEY;
@Slf4j @Slf4j
@AllArgsConstructor @AllArgsConstructor
public class RuleChainsEdgeEventFetcher extends BasePageableEdgeEventFetcher<RuleChain> { public class RuleChainsEdgeEventFetcher extends BasePageableEdgeEventFetcher<RuleChain> {
@ -41,7 +45,13 @@ public class RuleChainsEdgeEventFetcher extends BasePageableEdgeEventFetcher<Rul
@Override @Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, RuleChain ruleChain) { EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, RuleChain ruleChain) {
ObjectNode isRootBody = JacksonUtil.OBJECT_MAPPER.createObjectNode();
boolean isRoot = false;
try {
isRoot = ruleChain.getId().equals(edge.getRootRuleChainId());
} catch (Exception ignored) {}
isRootBody.put(EDGE_IS_ROOT_BODY_KEY, isRoot);
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN,
EdgeEventActionType.ADDED, ruleChain.getId(), null); EdgeEventActionType.ADDED, ruleChain.getId(), isRootBody);
} }
} }

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

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

4
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.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; 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.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -102,6 +103,9 @@ public interface TbNotificationEntityService {
void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo); void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo);
void notifyAlarmComment(Alarm alarm, AlarmComment alarmComment, ActionType actionType, User user);
<E extends HasName, I extends EntityId> void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId, <E extends HasName, I extends EntityId> void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId,
I entityId, E entity, User user, I entityId, E entity, User user,
ActionType actionType, boolean sendNotifyMsgToEdge, 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_COMMENT : ActionType.UPDATED_COMMENT;
UserId userId = user.getId();
alarmComment.setUserId(userId);
try {
AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment));
notificationEntityService.notifyAlarmComment(alarm, savedAlarmComment, actionType, user);
return savedAlarmComment;
} catch (Exception e) {
notificationEntityService.logEntityAction(alarm.getTenantId(), emptyId(EntityType.ALARM), alarm, actionType, user, e, alarmComment);
throw e;
}
}
@Override
public void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) {
alarmCommentService.deleteAlarmComment(alarm.getTenantId(), alarmComment.getId());
notificationEntityService.notifyAlarmComment(alarm, alarmComment, ActionType.DELETED_COMMENT, user);
}
}

18
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java

@ -24,6 +24,8 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -57,6 +59,14 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
long ackTs = System.currentTimeMillis(); long ackTs = System.currentTimeMillis();
ListenableFuture<Boolean> future = alarmSubscriptionService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs); ListenableFuture<Boolean> future = alarmSubscriptionService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs);
return Futures.transform(future, result -> { return Futures.transform(future, result -> {
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was acknowledged by user %s",
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString()))
.build();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
alarm.setAckTs(ackTs); alarm.setAckTs(ackTs);
alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK); alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK);
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ACK, user); notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ACK, user);
@ -69,6 +79,14 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
long clearTs = System.currentTimeMillis(); long clearTs = System.currentTimeMillis();
ListenableFuture<Boolean> future = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs); ListenableFuture<Boolean> future = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs);
return Futures.transform(future, result -> { return Futures.transform(future, result -> {
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was cleared by user %s",
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString()))
.build();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
alarm.setClearTs(clearTs); alarm.setClearTs(clearTs);
alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK);
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user); notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user);

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

@ -0,0 +1,27 @@
/**
* 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;
public interface TbAlarmCommentService {
AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException;
void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user);
}

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

@ -48,6 +48,9 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE
ActionType actionType = edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED; ActionType actionType = edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = edge.getTenantId(); TenantId tenantId = edge.getTenantId();
try { try {
if (actionType == ActionType.ADDED && edge.getRootRuleChainId() == null) {
edge.setRootRuleChainId(edgeTemplateRootRuleChain.getId());
}
Edge savedEdge = checkNotNull(edgeService.saveEdge(edge)); Edge savedEdge = checkNotNull(edgeService.saveEdge(edge));
EdgeId edgeId = savedEdge.getId(); EdgeId edgeId = savedEdge.getId();

18
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java

@ -23,8 +23,11 @@ import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
@ -42,6 +45,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
@ -62,6 +66,7 @@ import java.util.Optional;
public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService { public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService {
private final AlarmService alarmService; private final AlarmService alarmService;
private final AlarmCommentService alarmCommentService;
private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageReportClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService; private final TbApiUsageStateService apiUsageStateService;
@ -69,11 +74,13 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
PartitionService partitionService, PartitionService partitionService,
AlarmService alarmService, AlarmService alarmService,
TbApiUsageReportClient apiUsageClient, TbApiUsageReportClient apiUsageClient,
TbApiUsageStateService apiUsageStateService) { TbApiUsageStateService apiUsageStateService,
AlarmCommentService alarmCommentService) {
super(clusterService, partitionService); super(clusterService, partitionService);
this.alarmService = alarmService; this.alarmService = alarmService;
this.apiUsageClient = apiUsageClient; this.apiUsageClient = apiUsageClient;
this.apiUsageStateService = apiUsageStateService; this.apiUsageStateService = apiUsageStateService;
this.alarmCommentService = alarmCommentService;
} }
@Autowired(required = false) @Autowired(required = false)
@ -91,6 +98,15 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled()); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled());
if (result.isSuccessful()) { if (result.isSuccessful()) {
onAlarmUpdated(result); onAlarmUpdated(result);
AlarmSeverity oldSeverity = result.getOldSeverity();
if (oldSeverity != null && !oldSeverity.equals(result.getAlarm().getSeverity())) {
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm severity was updated from %s to %s", oldSeverity, result.getAlarm().getSeverity())))
.build();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
}
} }
if (result.isCreated()) { if (result.isCreated()) {
apiUsageClient.report(alarm.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT); apiUsageClient.report(alarm.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT);

2
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. 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: audit_logs:
partition_size: "${SQL_AUDIT_LOGS_PARTITION_SIZE_HOURS:168}" # Default value - 1 week 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 # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
batch_sort: "${SQL_BATCH_SORT:true}" batch_sort: "${SQL_BATCH_SORT:true}"
# Specify whether to remove null characters from strValue of attributes and timeseries before insert # Specify whether to remove null characters from strValue of attributes and timeseries before insert

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

@ -367,7 +367,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.verify(tbClusterService, times(cntTime)).pushMsgToCore(Mockito.any(ToDeviceActorNotificationMsg.class), Mockito.isNull()); 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, CustomerId customerId, UserId userId, String userName,
ActionType actionType, int cntTime, Object... additionalInfo) { ActionType actionType, int cntTime, Object... additionalInfo) {
ArgumentMatcher<HasName> matcherEntityEquals = entity == null ? Objects::isNull : argument -> argument.toString().equals(entity.toString()); ArgumentMatcher<HasName> matcherEntityEquals = entity == null ? Objects::isNull : argument -> argument.toString().equals(entity.toString());

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

@ -0,0 +1,363 @@
/**
* 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.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.AlarmCommentType;
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(alarm, alarm.getId(), tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ADDED_COMMENT, 1, createdComment);
}
@Test
public void testCreateAlarmCommentViaTenant() throws Exception {
loginTenantAdmin();
Mockito.reset(tbClusterService, auditLogService);
AlarmComment createdComment = createAlarmComment(alarm.getId());
Assert.assertEquals(AlarmCommentType.OTHER, createdComment.getType());
testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ADDED_COMMENT, 1, createdComment);
}
@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(alarm, alarm.getId(), tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.UPDATED_COMMENT, 1, savedComment);
}
@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(alarm, alarm.getId(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UPDATED_COMMENT, 1, updatedAlarmComment);
}
@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(alarm.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(alarm.getId(), savedComment);
}
@Test
public void testDeleteAlarmСommentViaCustomer() throws Exception {
loginCustomerUser();
AlarmComment alarmComment = createAlarmComment(alarm.getId());
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/alarm/" + alarm.getId() + "/comment/" + alarmComment.getId())
.andExpect(status().isOk());
testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.DELETED_COMMENT, 1, alarmComment);
}
@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());
testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED_COMMENT, 1, alarmComment);
}
@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;
}
}

14
application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java

@ -825,26 +825,30 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
assertThat(edgeImitator.waitForMessages()).as("await for messages on first connect").isTrue(); assertThat(edgeImitator.waitForMessages()).as("await for messages on first connect").isTrue();
assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("one msg during sync process").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("one msg during sync process").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("one msg during sync process, another from edge creation").hasSize(2); List<RuleChainUpdateMsg> ruleChainUpdateMsgs = edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class);
assertThat(ruleChainUpdateMsgs).as("one msg during sync process, another from edge creation").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("one msg during sync process for 'default' device profile").hasSize(3); assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("one msg during sync process for 'default' device profile").hasSize(3);
assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("one msg once device assigned to edge").hasSize(2); assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("one msg once device assigned to edge").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("two msgs during sync process for 'default' and 'test' asset profiles").hasSize(4); assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("two msgs during sync process for 'default' and 'test' asset profiles").hasSize(4);
assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("two msgs - one during sync process, and one more once asset assigned to edge").hasSize(2); assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("two msgs - one during sync process, and one more once asset assigned to edge").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("one msg during sync process for tenant admin user").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("one msg during sync process for tenant admin user").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update").hasSize(4); assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update").hasSize(4);
verifyRuleChainMsgsAreRoot(ruleChainUpdateMsgs);
edgeImitator.expectMessageAmount(14); edgeImitator.expectMessageAmount(14);
doPost("/api/edge/sync/" + edge.getId()); doPost("/api/edge/sync/" + edge.getId());
assertThat(edgeImitator.waitForMessages()).as("await for messages after edge sync rest api call").isTrue(); assertThat(edgeImitator.waitForMessages()).as("await for messages after edge sync rest api call").isTrue();
assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("queue msg").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("queue msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("rule chain msg").hasSize(1); ruleChainUpdateMsgs = edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class);
assertThat(ruleChainUpdateMsgs).as("rule chain msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("device profile msg").hasSize(2); assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("device profile msg").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("asset profile msg").hasSize(3); assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("asset profile msg").hasSize(3);
assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("asset update msg").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("asset update msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("user update msg").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("user update msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update msg").hasSize(4); assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update msg").hasSize(4);
assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("asset update msg").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("asset update msg").hasSize(1);
verifyRuleChainMsgsAreRoot(ruleChainUpdateMsgs);
edgeImitator.allowIgnoredTypes(); edgeImitator.allowIgnoredTypes();
try { try {
@ -860,6 +864,12 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
private void verifyRuleChainMsgsAreRoot(List<RuleChainUpdateMsg> ruleChainUpdateMsgs) {
for (RuleChainUpdateMsg ruleChainUpdateMsg : ruleChainUpdateMsgs) {
Assert.assertTrue(ruleChainUpdateMsg.getRoot());
}
}
@Test @Test
public void testDeleteEdgeWithDeleteRelationsOk() throws Exception { public void testDeleteEdgeWithDeleteRelationsOk() throws Exception {
EdgeId edgeId = savedEdge("Edge for Test WithRelationsOk").getId(); EdgeId edgeId = savedEdge("Edge for Test WithRelationsOk").getId();

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

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

@ -29,6 +29,8 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
@ -37,6 +39,8 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import java.util.UUID;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
@ -62,6 +66,8 @@ public class DefaultTbAlarmServiceTest {
@MockBean @MockBean
protected AlarmService alarmService; protected AlarmService alarmService;
@MockBean @MockBean
protected AlarmCommentService alarmCommentService;
@MockBean
protected AlarmSubscriptionService alarmSubscriptionService; protected AlarmSubscriptionService alarmSubscriptionService;
@MockBean @MockBean
protected CustomerService customerService; protected CustomerService customerService;
@ -88,8 +94,9 @@ public class DefaultTbAlarmServiceTest {
var alarm = new Alarm(); var alarm = new Alarm();
alarm.setStatus(AlarmStatus.ACTIVE_UNACK); alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
when(alarmSubscriptionService.ackAlarm(any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true)); when(alarmSubscriptionService.ackAlarm(any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true));
service.ack(alarm, new User()); service.ack(alarm, new User(new UserId(UUID.randomUUID())));
verify(alarmCommentService, times(1)).createOrUpdateAlarmComment(any(), any());
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any());
verify(alarmSubscriptionService, times(1)).ackAlarm(any(), any(), anyLong()); verify(alarmSubscriptionService, times(1)).ackAlarm(any(), any(), anyLong());
} }
@ -99,8 +106,9 @@ public class DefaultTbAlarmServiceTest {
var alarm = new Alarm(); var alarm = new Alarm();
alarm.setStatus(AlarmStatus.ACTIVE_ACK); alarm.setStatus(AlarmStatus.ACTIVE_ACK);
when(alarmSubscriptionService.clearAlarm(any(), any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true)); when(alarmSubscriptionService.clearAlarm(any(), any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true));
service.clear(alarm, new User()); service.clear(alarm, new User(new UserId(UUID.randomUUID())));
verify(alarmCommentService, times(1)).createOrUpdateAlarmComment(any(), any());
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any());
verify(alarmSubscriptionService, times(1)).clearAlarm(any(), any(), any(), anyLong()); verify(alarmSubscriptionService, times(1)).clearAlarm(any(), any(), any(), anyLong());
} }

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.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.doNothing;
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(alarmComment);
service.saveAlarmComment(alarm, alarmComment, new User());
verify(notificationEntityService, times(1)).notifyAlarmComment(any(), any(), any(), any());
}
@Test
public void testDelete() {
var alarmId = new AlarmId(UUID.randomUUID());
var alarmCommentId = new AlarmCommentId(UUID.randomUUID());
doNothing().when(alarmCommentService).deleteAlarmComment(Mockito.any(), eq(alarmCommentId));
service.deleteAlarmComment(new Alarm(alarmId), new AlarmComment(alarmCommentId), new User());
verify(notificationEntityService, times(1)).notifyAlarmComment(any(), any(), any(), any());
}
}

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/cache/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/cluster-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

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 {
AlarmComment createOrUpdateAlarmComment(TenantId tenantId, AlarmComment alarmComment);
void 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);
}

8
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java

@ -15,8 +15,10 @@
*/ */
package org.thingsboard.server.dao.alarm; package org.thingsboard.server.dao.alarm;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -24,11 +26,13 @@ import java.util.Collections;
import java.util.List; import java.util.List;
@Data @Data
@AllArgsConstructor
public class AlarmOperationResult { public class AlarmOperationResult {
private final AlarmInfo alarmInfo;
private final boolean successful; private final boolean successful;
private final boolean created; private final boolean created;
private final AlarmSeverity oldSeverity;
private final List<EntityId> propagatedEntitiesList; private final List<EntityId> propagatedEntitiesList;
private final AlarmInfo alarmInfo;
public AlarmOperationResult(Alarm alarm, boolean successful) { public AlarmOperationResult(Alarm alarm, boolean successful) {
this(new AlarmInfo(alarm, null, null, null, null, null), successful, Collections.emptyList()); this(new AlarmInfo(alarm, null, null, null, null, null), successful, Collections.emptyList());
@ -39,7 +43,7 @@ public class AlarmOperationResult {
} }
public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful, List<EntityId> propagatedEntitiesList) { public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful, List<EntityId> propagatedEntitiesList) {
this(alarmInfo, successful, false, propagatedEntitiesList); this(alarmInfo, successful, false, null, propagatedEntitiesList);
} }
public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful, boolean created, List<EntityId> propagatedEntitiesList) { public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful, boolean created, List<EntityId> propagatedEntitiesList) {

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.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm; 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.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; 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.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

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. System type means comment was created by TB. OTHER type means comment was created by user.", example = "SYSTEM/OTHER", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private AlarmCommentType type;
@ApiModelProperty(position = 6, value = "JSON object with text of comment.", dataType = "com.fasterxml.jackson.databind.JsonNode")
@NoXss
@Length(fieldName = "comment", max = 10000)
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;
}
}

22
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentType.java

@ -0,0 +1,22 @@
/**
* 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;
public enum AlarmCommentType {
SYSTEM, OTHER;
}

5
common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java

@ -50,7 +50,10 @@ public enum ActionType {
PROVISION_SUCCESS(false), PROVISION_SUCCESS(false),
PROVISION_FAILURE(false), PROVISION_FAILURE(false),
ASSIGNED_TO_EDGE(false), // log edge name ASSIGNED_TO_EDGE(false), // log edge name
UNASSIGNED_FROM_EDGE(false); UNASSIGNED_FROM_EDGE(false),
ADDED_COMMENT(false),
UPDATED_COMMENT(false),
DELETED_COMMENT(false);
private final boolean isRead; private final boolean isRead;

39
common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmCommentId.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.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 {
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));
}
}

2
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>common</artifactId> <artifactId>common</artifactId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/script/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/script/remote-js-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>script</artifactId> <artifactId>script</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.script</groupId> <groupId>org.thingsboard.common.script</groupId>

2
common/script/script-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>script</artifactId> <artifactId>script</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.script</groupId> <groupId>org.thingsboard.common.script</groupId>

2
common/stats/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>

2
common/transport/transport-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/version-control/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>dao</artifactId> <artifactId>dao</artifactId>

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

@ -0,0 +1,41 @@
/**
* 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);
}

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

@ -0,0 +1,115 @@
/**
* 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.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
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 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 DataValidator<AlarmComment> alarmCommentDataValidator;
@Override
public AlarmComment createOrUpdateAlarmComment(TenantId tenantId, AlarmComment alarmComment) {
alarmCommentDataValidator.validate(alarmComment, c -> tenantId);
if (alarmComment.getId() == null) {
return createAlarmComment(tenantId, alarmComment);
} else {
return updateAlarmComment(tenantId, alarmComment);
}
}
@Override
public void deleteAlarmComment(TenantId tenantId, AlarmCommentId alarmCommentId) {
log.debug("Deleting Alarm Comment with id: {}", alarmCommentId);
alarmCommentDao.deleteAlarmComment(tenantId, alarmCommentId);
}
@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 AlarmComment createAlarmComment(TenantId tenantId, AlarmComment alarmComment) {
log.debug("New Alarm comment : {}", alarmComment);
if (alarmComment.getType() == null) {
alarmComment.setType(AlarmCommentType.OTHER);
}
if (alarmComment.getId() == null) {
UUID uuid = Uuids.timeBased();
alarmComment.setId(new AlarmCommentId(uuid));
alarmComment.setCreatedTime(Uuids.unixTimestamp(uuid));
}
return alarmCommentDao.createAlarmComment(tenantId, alarmComment);
}
private AlarmComment 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();
((ObjectNode) comment).put("edited", "true");
((ObjectNode) comment).put("editedOn", System.currentTimeMillis());
existing.setComment(comment);
}
return alarmCommentDao.save(tenantId, existing);
}
return null;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -216,6 +216,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
boolean propagationEnabled = !oldAlarm.isPropagate() && newAlarm.isPropagate(); boolean propagationEnabled = !oldAlarm.isPropagate() && newAlarm.isPropagate();
boolean propagationToOwnerEnabled = !oldAlarm.isPropagateToOwner() && newAlarm.isPropagateToOwner(); boolean propagationToOwnerEnabled = !oldAlarm.isPropagateToOwner() && newAlarm.isPropagateToOwner();
boolean propagationToTenantEnabled = !oldAlarm.isPropagateToTenant() && newAlarm.isPropagateToTenant(); boolean propagationToTenantEnabled = !oldAlarm.isPropagateToTenant() && newAlarm.isPropagateToTenant();
AlarmSeverity oldAlarmSeverity = oldAlarm.getSeverity();
Alarm result = alarmDao.save(newAlarm.getTenantId(), merge(oldAlarm, newAlarm)); Alarm result = alarmDao.save(newAlarm.getTenantId(), merge(oldAlarm, newAlarm));
List<EntityId> propagatedEntitiesList; List<EntityId> propagatedEntitiesList;
if (propagationEnabled || propagationToOwnerEnabled || propagationToTenantEnabled) { if (propagationEnabled || propagationToOwnerEnabled || propagationToTenantEnabled) {
@ -228,8 +229,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} else { } else {
propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result)); propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result));
} }
// TODO oldAlarmSeverity
AlarmInfo alarmInfo = getAlarmInfo(newAlarm.getTenantId(), newAlarm); AlarmInfo alarmInfo = getAlarmInfo(newAlarm.getTenantId(), newAlarm);
return new AlarmOperationResult(alarmInfo, true, propagatedEntitiesList); return new AlarmOperationResult(alarmInfo, true, propagatedEntitiesList);
return new AlarmOperationResult(result, true, false, oldAlarmSeverity, propagatedEntitiesList);
} }
@Override @Override

7
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java

@ -30,6 +30,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.audit.ActionStatus; import org.thingsboard.server.common.data.audit.ActionStatus;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.audit.AuditLog;
@ -184,6 +185,12 @@ public class AuditLogServiceImpl implements AuditLogService {
} }
} }
break; break;
case ADDED_COMMENT:
case UPDATED_COMMENT:
case DELETED_COMMENT:
AlarmComment comment = extractParameter(AlarmComment.class, additionalInfo);
actionData.set("comment", comment.getComment());
break;
case DELETED: case DELETED:
case ACTIVATED: case ACTIVATED:
case SUSPENDED: case SUSPENDED:

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

@ -304,6 +304,14 @@ public class ModelConstants {
public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id"; 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. * Cassandra entity relation constants.
*/ */

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

@ -0,0 +1,98 @@
/**
* 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.alarm.AlarmCommentType;
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 AlarmCommentType 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());
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();
}
}

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

@ -0,0 +1,50 @@
/**
* 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;
@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);
}
}

15
dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.service; package org.thingsboard.server.dao.service;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.Length;
@ -23,15 +24,21 @@ import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; import javax.validation.ConstraintValidatorContext;
@Slf4j @Slf4j
public class StringLengthValidator implements ConstraintValidator<Length, String> { public class StringLengthValidator implements ConstraintValidator<Length, Object> {
private int max; private int max;
@Override @Override
public boolean isValid(String value, ConstraintValidatorContext context) { public boolean isValid(Object value, ConstraintValidatorContext context) {
if (StringUtils.isEmpty(value)) { String stringValue;
if (value instanceof CharSequence || value instanceof JsonNode) {
stringValue = value.toString();
} else {
return true; return true;
} }
return value.length() <= max; if (StringUtils.isEmpty(stringValue)) {
return true;
}
return stringValue.length() <= max;
} }
@Override @Override

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

99
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDao.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.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;
@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
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_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_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);

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

@ -63,6 +63,16 @@ CREATE TABLE IF NOT EXISTS alarm (
propagate_to_tenant boolean propagate_to_tenant boolean
); );
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(10000),
CONSTRAINT fk_alarm_comment_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE
) PARTITION BY RANGE (created_time);
CREATE TABLE IF NOT EXISTS entity_alarm ( CREATE TABLE IF NOT EXISTS entity_alarm (
tenant_id uuid NOT NULL, tenant_id uuid NOT NULL,
entity_type varchar(32), entity_type varchar(32),
@ -778,4 +788,4 @@ CREATE TABLE IF NOT EXISTS user_auth_settings (
created_time bigint NOT NULL, created_time bigint NOT NULL,
user_id uuid UNIQUE NOT NULL CONSTRAINT fk_user_auth_settings_user_id REFERENCES tb_user(id), user_id uuid UNIQUE NOT NULL CONSTRAINT fk_user_auth_settings_user_id REFERENCES tb_user(id),
two_fa_settings varchar two_fa_settings varchar
); );

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

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

@ -0,0 +1,164 @@
/**
* 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;
import static org.thingsboard.server.common.data.alarm.AlarmCommentType.OTHER;
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);
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);
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);
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);
Assert.assertNotNull(createdComment);
Assert.assertNotNull(createdComment.getId());
alarmCommentService.deleteAlarmComment(tenantId, createdComment.getId());
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 {
}

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

@ -0,0 +1,91 @@
/**
* 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.AlarmCommentType;
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, AlarmCommentType.OTHER);
saveAlarmComment(commentId2, alarmId1, userId, AlarmCommentType.OTHER);
saveAlarmComment(commentId3, alarmId2, userId, AlarmCommentType.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, AlarmCommentType 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);
}
}

2
msa/black-box-tests/pom.xml

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/js-executor/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-js-executor", "name": "thingsboard-js-executor",
"private": true, "private": true,
"version": "3.4.3", "version": "3.5.0",
"description": "ThingsBoard JavaScript Executor Microservice", "description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.ts", "main": "server.ts",
"bin": "server.js", "bin": "server.js",

2
msa/js-executor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>

2
msa/tb-node/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/tb/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/vc-executor-docker/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/vc-executor/pom.xml

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/web-ui/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-web-ui", "name": "thingsboard-web-ui",
"private": true, "private": true,
"version": "3.4.3", "version": "3.5.0",
"description": "ThingsBoard Web UI Microservice", "description": "ThingsBoard Web UI Microservice",
"main": "server.ts", "main": "server.ts",
"bin": "server.js", "bin": "server.js",

2
msa/web-ui/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

4
netty-mqtt/pom.xml

@ -19,11 +19,11 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>netty-mqtt</artifactId> <artifactId>netty-mqtt</artifactId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Netty MQTT Client</name> <name>Netty MQTT Client</name>

2
pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Thingsboard</name> <name>Thingsboard</name>

2
rest-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>rest-client</artifactId> <artifactId>rest-client</artifactId>

2
rule-engine/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>

2
rule-engine/rule-engine-api/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>
</parent> </parent>
<groupId>org.thingsboard.rule-engine</groupId> <groupId>org.thingsboard.rule-engine</groupId>

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

2
rule-engine/rule-engine-components/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>
</parent> </parent>
<groupId>org.thingsboard.rule-engine</groupId> <groupId>org.thingsboard.rule-engine</groupId>

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

@ -56,6 +56,7 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle; 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.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
@ -93,6 +94,8 @@ public class TenantIdLoaderTest {
@Mock @Mock
private RuleEngineAlarmService alarmService; private RuleEngineAlarmService alarmService;
@Mock @Mock
private AlarmCommentService alarmCommentService;
@Mock
private RuleChainService ruleChainService; private RuleChainService ruleChainService;
@Mock @Mock
private EntityViewService entityViewService; private EntityViewService entityViewService;

2
tools/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>tools</artifactId> <artifactId>tools</artifactId>

2
transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.3-SNAPSHOT</version> <version>3.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.transport</groupId> <groupId>org.thingsboard.transport</groupId>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save