diff --git a/application/pom.xml b/application/pom.xml
index 254c892f51..815243f85b 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
thingsboard
application
diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql
index ca57a80487..117f8f5fa3 100644
--- a/application/src/main/data/upgrade/3.4.3/schema_update.sql
+++ b/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;
--- ALARM ASSIGN TO USER END
\ No newline at end of file
+-- 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);
\ No newline at end of file
diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index 8b19a6954c..8fb9804e71 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
+import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
@@ -278,6 +279,10 @@ public class ActorSystemContext {
@Getter
private AlarmSubscriptionService alarmService;
+ @Autowired
+ @Getter
+ private AlarmCommentService alarmCommentService;
+
@Autowired
@Getter
private JsInvokeService jsInvokeService;
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index ba1c4e9796..b2338bd070 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -71,6 +71,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.TbMsgProcessingStackItem;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
+import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
@@ -591,6 +592,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getAlarmService();
}
+ @Override
+ public AlarmCommentService getAlarmCommentService() {
+ return mainCtx.getAlarmCommentService();
+ }
+
@Override
public RuleChainService getRuleChainService() {
return mainCtx.getRuleChainService();
diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java
new file mode 100644
index 0000000000..1af3c8c8bf
--- /dev/null
+++ b/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 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));
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index c68f546f77..faf9fa39be 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -54,6 +54,7 @@ import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
@@ -64,6 +65,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.edge.EdgeInfo;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
@@ -98,6 +100,7 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
@@ -200,6 +203,9 @@ public abstract class BaseController {
@Autowired
protected AlarmSubscriptionService alarmService;
+ @Autowired
+ protected AlarmCommentService alarmCommentService;
+
@Autowired
protected DeviceCredentialsService deviceCredentialsService;
@@ -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 {
return checkWidgetsBundleId(widgetsBundleId, operation, getCurrentUser());
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
index 1186d2a7c3..40ddf76142 100644
--- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
+++ b/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 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 ALARM_COMMENT_ID_PARAM_DESCRIPTION = "A string value representing the alarm comment id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ENTITY_ID_PARAM_DESCRIPTION = "A string value representing the entity id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String OTA_PACKAGE_ID_PARAM_DESCRIPTION = "A string value representing the ota package id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ENTITY_TYPE_PARAM_DESCRIPTION = "A string value representing the entity type. For example, 'DEVICE'";
@@ -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_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle";
protected static final String ALARM_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, startTs, endTs, type, ackTs, clearTs, severity, status";
+ protected static final String ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime";
protected static final String EVENT_SORT_PROPERTY_ALLOWABLE_VALUES = "ts, id";
protected static final String EDGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle";
protected static final String RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, root";
diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
index 6b6104b0e2..715e59f390 100644
--- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
+++ b/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 ...");
databaseEntitiesUpgradeService.upgradeDatabase("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...");
systemDataLoaderService.updateSystemWidgets();
break;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java
index 8d2a8adc51..91f453d3de 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java
+++ b/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;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge;
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.dao.rule.RuleChainService;
+import static org.thingsboard.server.service.edge.DefaultEdgeNotificationService.EDGE_IS_ROOT_BODY_KEY;
+
@Slf4j
@AllArgsConstructor
public class RuleChainsEdgeEventFetcher extends BasePageableEdgeEventFetcher {
@@ -41,7 +45,13 @@ public class RuleChainsEdgeEventFetcher extends BasePageableEdgeEventFetcher void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId,
I entityId, E entity, User user,
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java
index dc6e148cab..baf63738e2 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java
@@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@@ -102,6 +103,9 @@ public interface TbNotificationEntityService {
void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo);
+ void notifyAlarmComment(Alarm alarm, AlarmComment alarmComment, ActionType actionType, User user);
+
+
void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId,
I entityId, E entity, User user,
ActionType actionType, boolean sendNotifyMsgToEdge,
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java
new file mode 100644
index 0000000000..ee98af5f0f
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
index 023e144bf0..368253c075 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
+++ b/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.User;
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.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@@ -57,6 +59,14 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
long ackTs = System.currentTimeMillis();
ListenableFuture future = alarmSubscriptionService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs);
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.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK);
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ACK, user);
@@ -69,6 +79,14 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
long clearTs = System.currentTimeMillis();
ListenableFuture future = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs);
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.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK);
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user);
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java
new file mode 100644
index 0000000000..d8bb798d49
--- /dev/null
+++ b/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);
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java
index 697fe35a42..b3b70a174e 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java
+++ b/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;
TenantId tenantId = edge.getTenantId();
try {
+ if (actionType == ActionType.ADDED && edge.getRootRuleChainId() == null) {
+ edge.setRootRuleChainId(edgeTemplateRootRuleChain.getId());
+ }
Edge savedEdge = checkNotNull(edgeService.saveEdge(edge));
EdgeId edgeId = savedEdge.getId();
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
index dd4b9b6c55..c23f9a1272 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
+++ b/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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
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.AlarmQuery;
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.TopicPartitionInfo;
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.AlarmService;
import org.thingsboard.server.gen.transport.TransportProtos;
@@ -62,6 +66,7 @@ import java.util.Optional;
public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService {
private final AlarmService alarmService;
+ private final AlarmCommentService alarmCommentService;
private final TbApiUsageReportClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService;
@@ -69,11 +74,13 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
PartitionService partitionService,
AlarmService alarmService,
TbApiUsageReportClient apiUsageClient,
- TbApiUsageStateService apiUsageStateService) {
+ TbApiUsageStateService apiUsageStateService,
+ AlarmCommentService alarmCommentService) {
super(clusterService, partitionService);
this.alarmService = alarmService;
this.apiUsageClient = apiUsageClient;
this.apiUsageStateService = apiUsageStateService;
+ this.alarmCommentService = alarmCommentService;
}
@Autowired(required = false)
@@ -91,6 +98,15 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled());
if (result.isSuccessful()) {
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()) {
apiUsageClient.report(alarm.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT);
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 47273d03b5..c17d03b312 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -277,6 +277,8 @@ sql:
partition_size: "${SQL_EDGE_EVENTS_PARTITION_SIZE_HOURS:168}" # Number of hours to partition the events. The current value corresponds to one week.
audit_logs:
partition_size: "${SQL_AUDIT_LOGS_PARTITION_SIZE_HOURS:168}" # Default value - 1 week
+ alarm_comments:
+ partition_size: "${SQL_ALARM_COMMENTS_PARTITION_SIZE_HOURS:168}" # Default value - 1 week
# Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
batch_sort: "${SQL_BATCH_SORT:true}"
# Specify whether to remove null characters from strValue of attributes and timeseries before insert
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
index 8de7157b52..672e55825b 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
+++ b/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());
}
- private void testLogEntityAction(HasName entity, EntityId originatorId, TenantId tenantId,
+ protected void testLogEntityAction(HasName entity, EntityId originatorId, TenantId tenantId,
CustomerId customerId, UserId userId, String userName,
ActionType actionType, int cntTime, Object... additionalInfo) {
ArgumentMatcher matcherEntityEquals = entity == null ? Objects::isNull : argument -> argument.toString().equals(entity.toString());
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
new file mode 100644
index 0000000000..337abee74f
--- /dev/null
+++ b/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 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>() {}
+ );
+ 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 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 pageData = doGetTyped(
+ "/api/alarm/" + alarm.getId() + "/comment" + "?page=0&pageSize=1", new TypeReference>() {}
+ );
+
+ 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;
+ }
+}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
index fa466ce93f..9106b5d824 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
+++ b/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.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 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(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(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(AdminSettingsUpdateMsg.class)).as("admin setting update").hasSize(4);
+ verifyRuleChainMsgsAreRoot(ruleChainUpdateMsgs);
edgeImitator.expectMessageAmount(14);
doPost("/api/edge/sync/" + edge.getId());
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(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(AssetProfileUpdateMsg.class)).as("asset profile msg").hasSize(3);
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(AdminSettingsUpdateMsg.class)).as("admin setting update msg").hasSize(4);
assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("asset update msg").hasSize(1);
+ verifyRuleChainMsgsAreRoot(ruleChainUpdateMsgs);
edgeImitator.allowIgnoredTypes();
try {
@@ -860,6 +864,12 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
.andExpect(status().isOk());
}
+ private void verifyRuleChainMsgsAreRoot(List ruleChainUpdateMsgs) {
+ for (RuleChainUpdateMsg ruleChainUpdateMsg : ruleChainUpdateMsgs) {
+ Assert.assertTrue(ruleChainUpdateMsg.getRoot());
+ }
+ }
+
@Test
public void testDeleteEdgeWithDeleteRelationsOk() throws Exception {
EdgeId edgeId = savedEdge("Edge for Test WithRelationsOk").getId();
diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/AlarmCommentControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/AlarmCommentControllerSqlTest.java
new file mode 100644
index 0000000000..ecf2791c1c
--- /dev/null
+++ b/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 {
+}
diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
index dba6546fc0..bea64f12af 100644
--- a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
+++ b/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.AlarmStatus;
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.customer.CustomerService;
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.telemetry.AlarmSubscriptionService;
+import java.util.UUID;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -62,6 +66,8 @@ public class DefaultTbAlarmServiceTest {
@MockBean
protected AlarmService alarmService;
@MockBean
+ protected AlarmCommentService alarmCommentService;
+ @MockBean
protected AlarmSubscriptionService alarmSubscriptionService;
@MockBean
protected CustomerService customerService;
@@ -88,8 +94,9 @@ public class DefaultTbAlarmServiceTest {
var alarm = new Alarm();
alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
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(alarmSubscriptionService, times(1)).ackAlarm(any(), any(), anyLong());
}
@@ -99,8 +106,9 @@ public class DefaultTbAlarmServiceTest {
var alarm = new Alarm();
alarm.setStatus(AlarmStatus.ACTIVE_ACK);
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(alarmSubscriptionService, times(1)).clearAlarm(any(), any(), any(), anyLong());
}
diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java
new file mode 100644
index 0000000000..174d78c7ef
--- /dev/null
+++ b/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());
+ }
+}
\ No newline at end of file
diff --git a/common/actor/pom.xml b/common/actor/pom.xml
index 8f2c1e1b13..2b9b91db6c 100644
--- a/common/actor/pom.xml
+++ b/common/actor/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/cache/pom.xml b/common/cache/pom.xml
index e48d232b16..d26d4ff937 100644
--- a/common/cache/pom.xml
+++ b/common/cache/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml
index a8dfc522ce..226a274ba0 100644
--- a/common/cluster-api/pom.xml
+++ b/common/cluster-api/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml
index 57bb96d2c2..6e22b45768 100644
--- a/common/coap-server/pom.xml
+++ b/common/coap-server/pom.xml
@@ -22,7 +22,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml
index cba811343a..acf757439f 100644
--- a/common/dao-api/pom.xml
+++ b/common/dao-api/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java
new file mode 100644
index 0000000000..d4ffee4169
--- /dev/null
+++ b/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 findAlarmComments(TenantId tenantId, AlarmId alarmId, PageLink pageLink);
+
+ ListenableFuture findAlarmCommentByIdAsync(TenantId tenantId, AlarmCommentId alarmCommentId);
+
+ AlarmComment findAlarmCommentById(TenantId tenantId, AlarmCommentId alarmCommentId);
+
+}
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
index 10fac5de08..b47aba3d88 100644
--- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
+++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
@@ -15,8 +15,10 @@
*/
package org.thingsboard.server.dao.alarm;
+import lombok.AllArgsConstructor;
import lombok.Data;
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.id.EntityId;
@@ -24,11 +26,13 @@ import java.util.Collections;
import java.util.List;
@Data
+@AllArgsConstructor
public class AlarmOperationResult {
+ private final AlarmInfo alarmInfo;
private final boolean successful;
private final boolean created;
+ private final AlarmSeverity oldSeverity;
private final List propagatedEntitiesList;
- private final AlarmInfo alarmInfo;
public AlarmOperationResult(Alarm alarm, boolean successful) {
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 propagatedEntitiesList) {
- this(alarmInfo, successful, false, propagatedEntitiesList);
+ this(alarmInfo, successful, false, null, propagatedEntitiesList);
}
public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful, boolean created, List propagatedEntitiesList) {
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
index 848d6dd5b1..d2bc6fcd47 100644
--- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
+++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
@@ -18,12 +18,12 @@ package org.thingsboard.server.dao.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
diff --git a/common/data/pom.xml b/common/data/pom.xml
index 9a95c08b65..6464c80471 100644
--- a/common/data/pom.xml
+++ b/common/data/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java
new file mode 100644
index 0000000000..0393c630a9
--- /dev/null
+++ b/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 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();
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentInfo.java
new file mode 100644
index 0000000000..66300e4cdb
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentType.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentType.java
new file mode 100644
index 0000000000..e9d4c34105
--- /dev/null
+++ b/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;
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
index 4b666fa425..c8fabb7fba 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
@@ -50,7 +50,10 @@ public enum ActionType {
PROVISION_SUCCESS(false),
PROVISION_FAILURE(false),
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;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmCommentId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmCommentId.java
new file mode 100644
index 0000000000..1ceeab2f4d
--- /dev/null
+++ b/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));
+ }
+}
diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml
index e09de1135f..daad429299 100644
--- a/common/edge-api/pom.xml
+++ b/common/edge-api/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/message/pom.xml b/common/message/pom.xml
index 5176e4af1a..72ea8ae0f3 100644
--- a/common/message/pom.xml
+++ b/common/message/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/pom.xml b/common/pom.xml
index c7b6d86eaf..84ca8fdcb2 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
thingsboard
common
diff --git a/common/queue/pom.xml b/common/queue/pom.xml
index 8485c2d63d..90911569bc 100644
--- a/common/queue/pom.xml
+++ b/common/queue/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/script/pom.xml b/common/script/pom.xml
index a849cf2684..0463e2d2e9 100644
--- a/common/script/pom.xml
+++ b/common/script/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml
index 651eb153ed..8190cb18d0 100644
--- a/common/script/remote-js-client/pom.xml
+++ b/common/script/remote-js-client/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
script
org.thingsboard.common.script
diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml
index 01fef3059b..e979207c10 100644
--- a/common/script/script-api/pom.xml
+++ b/common/script/script-api/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
script
org.thingsboard.common.script
diff --git a/common/stats/pom.xml b/common/stats/pom.xml
index 2fcac6e059..d72ca8e81c 100644
--- a/common/stats/pom.xml
+++ b/common/stats/pom.xml
@@ -22,7 +22,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml
index f4089d9a79..b1ee7f3774 100644
--- a/common/transport/coap/pom.xml
+++ b/common/transport/coap/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.common.transport
diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml
index c6c8f9869d..ee2c13d8b5 100644
--- a/common/transport/http/pom.xml
+++ b/common/transport/http/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.common.transport
diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml
index 3c6fae874f..b6cb46e8cf 100644
--- a/common/transport/lwm2m/pom.xml
+++ b/common/transport/lwm2m/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.common.transport
diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml
index 9442a66646..4f7384bb82 100644
--- a/common/transport/mqtt/pom.xml
+++ b/common/transport/mqtt/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.common.transport
diff --git a/common/transport/pom.xml b/common/transport/pom.xml
index 47432f47f2..11cd48b9f1 100644
--- a/common/transport/pom.xml
+++ b/common/transport/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml
index 939eecb009..a15e7feeef 100644
--- a/common/transport/snmp/pom.xml
+++ b/common/transport/snmp/pom.xml
@@ -21,7 +21,7 @@
org.thingsboard.common
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml
index 06fbfc5771..fa1e93ea4c 100644
--- a/common/transport/transport-api/pom.xml
+++ b/common/transport/transport-api/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.common
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.common.transport
diff --git a/common/util/pom.xml b/common/util/pom.xml
index 1a178b4c1e..13d9c502cf 100644
--- a/common/util/pom.xml
+++ b/common/util/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml
index 6ff68da760..2ab1f45eec 100644
--- a/common/version-control/pom.xml
+++ b/common/version-control/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
common
org.thingsboard.common
diff --git a/dao/pom.xml b/dao/pom.xml
index 5335492759..95a337c9c2 100644
--- a/dao/pom.xml
+++ b/dao/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
thingsboard
dao
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentDao.java
new file mode 100644
index 0000000000..8db3fbf71f
--- /dev/null
+++ b/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 createAlarmComment(TenantId tenantId, AlarmComment alarmComment);
+
+ void deleteAlarmComment(TenantId tenantId, AlarmCommentId alarmCommentId);
+
+ AlarmComment findAlarmCommentById(TenantId tenantId, UUID key);
+
+ PageData findAlarmComments(TenantId tenantId, AlarmId id, PageLink pageLink);
+
+ ListenableFuture findAlarmCommentByIdAsync(TenantId tenantId, UUID key);
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java
new file mode 100644
index 0000000000..7cc547536a
--- /dev/null
+++ b/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 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 findAlarmComments(TenantId tenantId, AlarmId alarmId, PageLink pageLink) {
+ log.trace("Executing findAlarmComments by alarmId [{}]", alarmId);
+ return alarmCommentDao.findAlarmComments(tenantId, alarmId, pageLink);
+ }
+
+ @Override
+ public ListenableFuture 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;
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
index e1b38a0dd5..6790c6912b 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
+++ b/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 propagationToOwnerEnabled = !oldAlarm.isPropagateToOwner() && newAlarm.isPropagateToOwner();
boolean propagationToTenantEnabled = !oldAlarm.isPropagateToTenant() && newAlarm.isPropagateToTenant();
+ AlarmSeverity oldAlarmSeverity = oldAlarm.getSeverity();
Alarm result = alarmDao.save(newAlarm.getTenantId(), merge(oldAlarm, newAlarm));
List propagatedEntitiesList;
if (propagationEnabled || propagationToOwnerEnabled || propagationToTenantEnabled) {
@@ -228,8 +229,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} else {
propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result));
}
+ // TODO oldAlarmSeverity
AlarmInfo alarmInfo = getAlarmInfo(newAlarm.getTenantId(), newAlarm);
return new AlarmOperationResult(alarmInfo, true, propagatedEntitiesList);
+ return new AlarmOperationResult(result, true, false, oldAlarmSeverity, propagatedEntitiesList);
}
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
index 90b332213d..c1ef65b05b 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
+++ b/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.HasName;
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.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog;
@@ -184,6 +185,12 @@ public class AuditLogServiceImpl implements AuditLogService {
}
}
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 ACTIVATED:
case SUSPENDED:
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
index da2ffc7c89..9c04c1397d 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
@@ -304,6 +304,14 @@ public class ModelConstants {
public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id";
+ public static final String ALARM_COMMENT_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
+ public static final String ALARM_COMMENT_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
+ public static final String ALARM_COMMENT_COLUMN_FAMILY_NAME = "alarm_comment";
+ public static final String ALARM_COMMENT_ALARM_ID = "alarm_id";
+ public static final String ALARM_COMMENT_USER_ID = USER_ID_PROPERTY;
+ public static final String ALARM_COMMENT_TYPE = "type";
+ public static final String ALARM_COMMENT_COMMENT = "comment";
+
/**
* Cassandra entity relation constants.
*/
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java
new file mode 100644
index 0000000000..e81558cac5
--- /dev/null
+++ b/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 extends BaseSqlEntity implements BaseEntity {
+
+ @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;
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentEntity.java
new file mode 100644
index 0000000000..6f9a507cbd
--- /dev/null
+++ b/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 {
+
+ public AlarmCommentEntity() {
+ super();
+ }
+
+ public AlarmCommentEntity(AlarmCommentInfo alarmCommentInfo) {
+ super(alarmCommentInfo);
+ }
+
+ public AlarmCommentEntity(AlarmComment alarmComment) {
+ super(alarmComment);
+ }
+
+ @Override
+ public AlarmComment toData() {
+ return super.toAlarmComment();
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentInfoEntity.java
new file mode 100644
index 0000000000..1d19ec9388
--- /dev/null
+++ b/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 {
+
+ 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);
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java
index 6c12c0cebd..a90fba0ead 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.service;
+import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.validation.Length;
@@ -23,15 +24,21 @@ import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
@Slf4j
-public class StringLengthValidator implements ConstraintValidator {
+public class StringLengthValidator implements ConstraintValidator {
private int max;
@Override
- public boolean isValid(String value, ConstraintValidatorContext context) {
- if (StringUtils.isEmpty(value)) {
+ public boolean isValid(Object value, ConstraintValidatorContext context) {
+ String stringValue;
+ if (value instanceof CharSequence || value instanceof JsonNode) {
+ stringValue = value.toString();
+ } else {
return true;
}
- return value.length() <= max;
+ if (StringUtils.isEmpty(stringValue)) {
+ return true;
+ }
+ return stringValue.length() <= max;
}
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmCommentDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmCommentDataValidator.java
new file mode 100644
index 0000000000..d85a58bc6d
--- /dev/null
+++ b/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 {
+
+ @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!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmCommentRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmCommentRepository.java
new file mode 100644
index 0000000000..19dfd6ec2e
--- /dev/null
+++ b/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 {
+
+ @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 findAllByAlarmId(@Param("alarmId") UUID alarmId,
+ Pageable pageable);
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDao.java
new file mode 100644
index 0000000000..ef43fd4df5
--- /dev/null
+++ b/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 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 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 findAlarmCommentByIdAsync(TenantId tenantId, UUID key) {
+ log.trace("Try to find alarm comment by id using [{}]", key);
+ return findByIdAsync(tenantId, key);
+ }
+
+ @Override
+ protected Class getEntityClass() {
+ return AlarmCommentEntity.class;
+ }
+
+ @Override
+ protected JpaRepository getRepository() {
+ return alarmCommentRepository;
+ }
+}
diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql
index e4c766e81d..dc72e4cdc3 100644
--- a/dao/src/main/resources/sql/schema-entities-idx.sql
+++ b/dao/src/main/resources/sql/schema-entities-idx.sql
@@ -79,3 +79,5 @@ CREATE INDEX IF NOT EXISTS idx_rule_node_external_id ON rule_node(rule_chain_id,
CREATE INDEX IF NOT EXISTS idx_rule_node_type ON rule_node(type);
CREATE INDEX IF NOT EXISTS idx_api_usage_state_entity_id ON api_usage_state(entity_id);
+
+CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id);
diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql
index 5ceb03288f..2a7e0df528 100644
--- a/dao/src/main/resources/sql/schema-entities.sql
+++ b/dao/src/main/resources/sql/schema-entities.sql
@@ -63,6 +63,16 @@ CREATE TABLE IF NOT EXISTS alarm (
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 (
tenant_id uuid NOT NULL,
entity_type varchar(32),
@@ -778,4 +788,4 @@ CREATE TABLE IF NOT EXISTS user_auth_settings (
created_time bigint NOT NULL,
user_id uuid UNIQUE NOT NULL CONSTRAINT fk_user_auth_settings_user_id REFERENCES tb_user(id),
two_fa_settings varchar
-);
+);
\ No newline at end of file
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
index 5ea3874d19..61147120bc 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
@@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
+import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
@@ -149,6 +150,8 @@ public abstract class AbstractServiceTest {
@Autowired
protected AlarmService alarmService;
+ @Autowired
+ protected AlarmCommentService alarmCommentService;
@Autowired
protected RuleChainService ruleChainService;
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java
new file mode 100644
index 0000000000..914c360d86
--- /dev/null
+++ b/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 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 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);
+ }
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/AlarmCommentServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/sql/AlarmCommentServiceSqlTest.java
new file mode 100644
index 0000000000..510b8659e9
--- /dev/null
+++ b/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 {
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmCommentDaoTest.java
new file mode 100644
index 0000000000..1a37e3af4b
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml
index eb7872ef7a..a7889dbe8c 100644
--- a/msa/black-box-tests/pom.xml
+++ b/msa/black-box-tests/pom.xml
@@ -21,7 +21,7 @@
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json
index e094dcc4e5..add59eb610 100644
--- a/msa/js-executor/package.json
+++ b/msa/js-executor/package.json
@@ -1,7 +1,7 @@
{
"name": "thingsboard-js-executor",
"private": true,
- "version": "3.4.3",
+ "version": "3.5.0",
"description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.ts",
"bin": "server.js",
diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml
index 860eb3891a..226e48a375 100644
--- a/msa/js-executor/pom.xml
+++ b/msa/js-executor/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/pom.xml b/msa/pom.xml
index 9e628b0f70..09e890cf10 100644
--- a/msa/pom.xml
+++ b/msa/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
thingsboard
msa
diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml
index 7119da74d8..806374f8ba 100644
--- a/msa/tb-node/pom.xml
+++ b/msa/tb-node/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml
index fe79951126..4c94013482 100644
--- a/msa/tb/pom.xml
+++ b/msa/tb/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml
index 82d7e02da6..ceeec9ebce 100644
--- a/msa/transport/coap/pom.xml
+++ b/msa/transport/coap/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.msa
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.msa.transport
diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml
index a596c5000f..de388ba261 100644
--- a/msa/transport/http/pom.xml
+++ b/msa/transport/http/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.msa
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.msa.transport
diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml
index 0a43e7deb5..526dc66605 100644
--- a/msa/transport/lwm2m/pom.xml
+++ b/msa/transport/lwm2m/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.msa
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.msa.transport
diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml
index 2fe358fe5d..1e91823e8d 100644
--- a/msa/transport/mqtt/pom.xml
+++ b/msa/transport/mqtt/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard.msa
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.msa.transport
diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml
index 3339d93577..df2b9943a9 100644
--- a/msa/transport/pom.xml
+++ b/msa/transport/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml
index 1a352f47fb..3a60f83568 100644
--- a/msa/transport/snmp/pom.xml
+++ b/msa/transport/snmp/pom.xml
@@ -21,7 +21,7 @@
org.thingsboard.msa
transport
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
org.thingsboard.msa.transport
diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml
index 05884931d3..b1241b330a 100644
--- a/msa/vc-executor-docker/pom.xml
+++ b/msa/vc-executor-docker/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml
index 31c96c583c..61468af189 100644
--- a/msa/vc-executor/pom.xml
+++ b/msa/vc-executor/pom.xml
@@ -21,7 +21,7 @@
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json
index 035b8a3072..18cc80481f 100644
--- a/msa/web-ui/package.json
+++ b/msa/web-ui/package.json
@@ -1,7 +1,7 @@
{
"name": "thingsboard-web-ui",
"private": true,
- "version": "3.4.3",
+ "version": "3.5.0",
"description": "ThingsBoard Web UI Microservice",
"main": "server.ts",
"bin": "server.js",
diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml
index 5a0a444687..4372bbcf0d 100644
--- a/msa/web-ui/pom.xml
+++ b/msa/web-ui/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
msa
org.thingsboard.msa
diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml
index 99d05fc583..c246ab7c5c 100644
--- a/netty-mqtt/pom.xml
+++ b/netty-mqtt/pom.xml
@@ -19,11 +19,11 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
thingsboard
netty-mqtt
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
Netty MQTT Client
diff --git a/pom.xml b/pom.xml
index 7f8a99f027..991a95d1b6 100755
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
pom
Thingsboard
diff --git a/rest-client/pom.xml b/rest-client/pom.xml
index 24f8453786..f2b1eca351 100644
--- a/rest-client/pom.xml
+++ b/rest-client/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
thingsboard
rest-client
diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml
index 126885cd22..d1f2bacea6 100644
--- a/rule-engine/pom.xml
+++ b/rule-engine/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
thingsboard
rule-engine
diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml
index 665158a44f..5016186a5c 100644
--- a/rule-engine/rule-engine-api/pom.xml
+++ b/rule-engine/rule-engine-api/pom.xml
@@ -22,7 +22,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
rule-engine
org.thingsboard.rule-engine
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
index 1786fd08be..332887d081 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
@@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.rule.RuleNodeState;
import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
+import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
@@ -237,6 +238,8 @@ public interface TbContext {
RuleEngineAlarmService getAlarmService();
+ AlarmCommentService getAlarmCommentService();
+
RuleChainService getRuleChainService();
RuleEngineRpcService getRpcService();
diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml
index d32d8163a6..a79134b811 100644
--- a/rule-engine/rule-engine-components/pom.xml
+++ b/rule-engine/rule-engine-components/pom.xml
@@ -22,7 +22,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
rule-engine
org.thingsboard.rule-engine
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java
index 8a53148204..08081ff961 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java
+++ b/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.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
@@ -93,6 +94,8 @@ public class TenantIdLoaderTest {
@Mock
private RuleEngineAlarmService alarmService;
@Mock
+ private AlarmCommentService alarmCommentService;
+ @Mock
private RuleChainService ruleChainService;
@Mock
private EntityViewService entityViewService;
diff --git a/tools/pom.xml b/tools/pom.xml
index d414a7a777..bb9c5f9958 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
thingsboard
tools
diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml
index 2640d1d80d..a915e0ef1a 100644
--- a/transport/coap/pom.xml
+++ b/transport/coap/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.transport
diff --git a/transport/http/pom.xml b/transport/http/pom.xml
index 7820c585ec..71869bf1b0 100644
--- a/transport/http/pom.xml
+++ b/transport/http/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.transport
diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml
index b36c8372ef..bd1bb0500c 100644
--- a/transport/lwm2m/pom.xml
+++ b/transport/lwm2m/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.transport
diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml
index 91ce12908b..55d5d04c7c 100644
--- a/transport/mqtt/pom.xml
+++ b/transport/mqtt/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
org.thingsboard.transport
diff --git a/transport/pom.xml b/transport/pom.xml
index 26af07f6a2..318bc794d1 100644
--- a/transport/pom.xml
+++ b/transport/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
thingsboard
transport
diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml
index 0701f7aa57..9aa4039b31 100644
--- a/transport/snmp/pom.xml
+++ b/transport/snmp/pom.xml
@@ -21,7 +21,7 @@
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
transport
diff --git a/ui-ngx/package.json b/ui-ngx/package.json
index 6b8112adad..0758e39948 100644
--- a/ui-ngx/package.json
+++ b/ui-ngx/package.json
@@ -1,6 +1,6 @@
{
"name": "thingsboard",
- "version": "3.4.3",
+ "version": "3.5.0",
"scripts": {
"ng": "ng",
"start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open",
diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml
index f78d168144..6361c082e5 100644
--- a/ui-ngx/pom.xml
+++ b/ui-ngx/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.4.3-SNAPSHOT
+ 3.5.0-SNAPSHOT
thingsboard
org.thingsboard
diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts
index b66399de6c..035381af69 100644
--- a/ui-ngx/src/app/core/http/entity.service.ts
+++ b/ui-ngx/src/app/core/http/entity.service.ts
@@ -869,8 +869,26 @@ export class EntityService {
};
aliasInfo.currentEntity = null;
if (!aliasInfo.resolveMultiple && aliasInfo.entityFilter) {
- return this.findSingleEntityInfoByEntityFilter(aliasInfo.entityFilter,
- {ignoreLoading: true, ignoreErrors: true}).pipe(
+ let currentEntity: EntityInfo = null;
+ if (result.stateEntity && aliasInfo.entityFilter.type === AliasFilterType.singleEntity) {
+ if (stateParams) {
+ let targetParams = stateParams;
+ if (result.entityParamName && result.entityParamName.length) {
+ targetParams = stateParams[result.entityParamName];
+ }
+ if (targetParams && targetParams.entityId) {
+ currentEntity = {
+ id: targetParams.entityId.id,
+ entityType: targetParams.entityId.entityType as EntityType,
+ name: targetParams.entityName,
+ label: targetParams.entityLabel
+ };
+ }
+ }
+ }
+ const entityInfoObservable = currentEntity ? of(currentEntity) : this.findSingleEntityInfoByEntityFilter(aliasInfo.entityFilter,
+ {ignoreLoading: true, ignoreErrors: true});
+ return entityInfoObservable.pipe(
map((entity) => {
aliasInfo.currentEntity = entity;
return aliasInfo;
diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts
index 31788122a9..dd9449032e 100644
--- a/ui-ngx/src/app/core/http/widget.service.ts
+++ b/ui-ngx/src/app/core/http/widget.service.ts
@@ -16,7 +16,7 @@
import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
-import { Observable, of, ReplaySubject, Subject } from 'rxjs';
+import { Observable, of, ReplaySubject } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
@@ -43,15 +43,14 @@ import { ActivationEnd, Router } from '@angular/router';
})
export class WidgetService {
- private widgetTypeUpdatedSubject = new Subject();
- private widgetsBundleDeletedSubject = new Subject();
-
private allWidgetsBundles: Array;
private systemWidgetsBundles: Array;
private tenantWidgetsBundles: Array;
private widgetTypeInfosCache = new Map>();
+ private widgetsInfoInMemoryCache = new Map();
+
private loadWidgetsBundleCacheSubject: ReplaySubject;
constructor(
@@ -117,7 +116,7 @@ export class WidgetService {
defaultHttpOptionsFromConfig(config)).pipe(
tap(() => {
this.invalidateWidgetsBundleCache();
- this.widgetsBundleDeletedSubject.next(widgetsBundle);
+ this.widgetsBundleDeleted(widgetsBundle);
})
);
}
@@ -217,7 +216,7 @@ export class WidgetService {
return this.http.post('/api/widgetType', widgetTypeDetails,
defaultHttpOptionsFromConfig(config)).pipe(
tap((savedWidgetType) => {
- this.widgetTypeUpdatedSubject.next(savedWidgetType);
+ this.widgetTypeUpdated(savedWidgetType);
}));
}
@@ -226,7 +225,7 @@ export class WidgetService {
return this.http.post('/api/widgetType', widgetTypeDetails,
defaultHttpOptionsFromConfig(config)).pipe(
tap((savedWidgetType) => {
- this.widgetTypeUpdatedSubject.next(savedWidgetType);
+ this.widgetTypeUpdated(savedWidgetType);
}));
}
@@ -237,7 +236,7 @@ export class WidgetService {
return this.http.delete(`/api/widgetType/${widgetTypeInstance.id.id}`,
defaultHttpOptionsFromConfig(config)).pipe(
tap(() => {
- this.widgetTypeUpdatedSubject.next(widgetTypeInstance);
+ this.widgetTypeUpdated(widgetTypeInstance);
})
);
}
@@ -263,12 +262,40 @@ export class WidgetService {
);
}
- public onWidgetTypeUpdated(): Observable {
- return this.widgetTypeUpdatedSubject.asObservable();
+ public createWidgetInfoCacheKey(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): string {
+ return `${isSystem ? 'sys_' : ''}${bundleAlias}_${widgetTypeAlias}`;
+ }
+
+ public getWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): WidgetInfo | undefined {
+ const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
+ return this.widgetsInfoInMemoryCache.get(key);
}
- public onWidgetBundleDeleted(): Observable {
- return this.widgetsBundleDeletedSubject.asObservable();
+ public putWidgetInfoToCache(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) {
+ const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
+ this.widgetsInfoInMemoryCache.set(key, widgetInfo);
+ }
+
+ private widgetTypeUpdated(updatedWidgetType: WidgetType): void {
+ this.deleteWidgetInfoFromCache(updatedWidgetType.bundleAlias, updatedWidgetType.alias, updatedWidgetType.tenantId.id === NULL_UUID);
+ }
+
+ private widgetsBundleDeleted(widgetsBundle: WidgetsBundle): void {
+ this.deleteWidgetsBundleFromCache(widgetsBundle.alias, widgetsBundle.tenantId.id === NULL_UUID);
+ }
+
+ private deleteWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) {
+ const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
+ this.widgetsInfoInMemoryCache.delete(key);
+ }
+
+ private deleteWidgetsBundleFromCache(bundleAlias: string, isSystem: boolean) {
+ const key = (isSystem ? 'sys_' : '') + bundleAlias;
+ this.widgetsInfoInMemoryCache.forEach((widgetInfo, cacheKey) => {
+ if (cacheKey.startsWith(key)) {
+ this.widgetsInfoInMemoryCache.delete(cacheKey);
+ }
+ });
}
private loadWidgetsBundleCache(config?: RequestConfig): Observable {
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
index ca1bae3de7..4c641ad1d7 100644
--- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
+++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
@@ -135,7 +135,7 @@
(click)="openDashboardSettings($event)">
settings
-
+ [ngStyle]="{width: mainLayoutSize.width,
+ height: mainLayoutSize.height}">
this.dashboard,
@@ -402,6 +410,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
.observe(MediaBreakpoints['gt-sm'])
.subscribe((state: BreakpointState) => {
this.isMobile = !state.matches;
+ this.updateLayoutSizes();
}
));
if (this.isMobileApp && this.syncStateWithQueryParam) {
@@ -414,6 +423,13 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}
}
+ ngAfterViewInit() {
+ this.dashboardResize$ = new ResizeObserver(() => {
+ this.updateLayoutSizes();
+ });
+ this.dashboardResize$.observe(this.dashboardContainer.nativeElement);
+ }
+
private init(data: DashboardPageInitData) {
this.reset();
@@ -537,6 +553,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
subscription.unsubscribe();
});
this.rxSubscriptions.length = 0;
+ if (this.dashboardResize$) {
+ this.dashboardResize$.disconnect();
+ }
}
public runChangeDetection() {
@@ -679,28 +698,48 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.mobileService.onDashboardRightLayoutChanged(this.isRightLayoutOpened);
}
- public mainLayoutWidth(): string {
- if (this.isEditingWidget && this.editingLayoutCtx.id === 'main') {
- return '100%';
- } else {
- return this.layouts.right.show && !this.isMobile ? this.calculateWidth('main') : '100%';
+ private updateLayoutSizes() {
+ let changeMainLayoutSize = false;
+ let changeRightLayoutSize = false;
+ if (this.dashboardCtx.state) {
+ changeMainLayoutSize = this.updateMainLayoutSize();
+ changeRightLayoutSize = this.updateRightLayoutSize();
+ }
+ if (changeMainLayoutSize || changeRightLayoutSize) {
+ this.cd.markForCheck();
}
}
- public mainLayoutHeight(): string {
+ private updateMainLayoutSize(): boolean {
+ const prevMainLayoutWidth = this.mainLayoutSize.width;
+ const prevMainLayoutHeight = this.mainLayoutSize.height;
+ if (this.isEditingWidget && this.editingLayoutCtx.id === 'main') {
+ this.mainLayoutSize.width = '100%';
+ } else {
+ this.mainLayoutSize.width = this.layouts.right.show && !this.isMobile ? this.calculateWidth('main') : '100%';
+ }
if (!this.isEditingWidget || this.editingLayoutCtx.id === 'main') {
- return '100%';
+ this.mainLayoutSize.height = '100%';
} else {
- return '0px';
+ this.mainLayoutSize.height = '0px';
}
+ return prevMainLayoutWidth !== this.mainLayoutSize.width || prevMainLayoutHeight !== this.mainLayoutSize.height;
}
- public rightLayoutWidth(): string {
+ private updateRightLayoutSize(): boolean {
+ const prevRightLayoutWidth = this.rightLayoutSize.width;
+ const prevRightLayoutHeight = this.rightLayoutSize.height;
if (this.isEditingWidget && this.editingLayoutCtx.id === 'right') {
- return '100%';
+ this.rightLayoutSize.width = '100%';
+ } else {
+ this.rightLayoutSize.width = this.isMobile ? '100%' : this.calculateWidth('right');
+ }
+ if (!this.isEditingWidget || this.editingLayoutCtx.id === 'right') {
+ this.rightLayoutSize.height = '100%';
} else {
- return this.isMobile ? '100%' : this.calculateWidth('right');
+ this.rightLayoutSize.height = '0px';
}
+ return prevRightLayoutWidth !== this.rightLayoutSize.width || prevRightLayoutHeight !== this.rightLayoutSize.height;
}
private calculateWidth(layout: DashboardLayoutId): string {
@@ -743,14 +782,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}
}
- public rightLayoutHeight(): string {
- if (!this.isEditingWidget || this.editingLayoutCtx.id === 'right') {
- return '100%';
- } else {
- return '0px';
- }
- }
-
public isPublicUser(): boolean {
return this.authUser.isPublic;
}
@@ -977,6 +1008,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
layout.layoutCtx.ctrl.reload();
}
layout.layoutCtx.ignoreLoading = true;
+ this.updateLayoutSizes();
}
private setEditMode(isEdit: boolean, revert: boolean) {
@@ -1191,6 +1223,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.editingLayoutCtx = null;
this.editingWidgetSubtitle = null;
this.isEditingWidget = false;
+ this.updateLayoutSizes();
this.resetHighlight();
this.forceDashboardMobileMode = false;
}
@@ -1216,6 +1249,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.editingWidgetSubtitle = this.widgetComponentService.getInstantWidgetInfo(this.editingWidget).widgetName;
this.forceDashboardMobileMode = true;
this.isEditingWidget = true;
+ this.updateLayoutSizes();
if (layoutCtx) {
const delayOffset = transition ? 350 : 0;
const delay = transition ? 400 : 300;
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts
index 4d593a7c57..801238d602 100644
--- a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts
+++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts
@@ -297,6 +297,7 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent();
-
private widgetsInfoFetchQueue = new Map>>();
private init$: Observable;
@@ -77,14 +74,6 @@ export class WidgetComponentService {
this.cssParser.testMode = false;
- this.widgetService.onWidgetTypeUpdated().subscribe((widgetType) => {
- this.deleteWidgetInfoFromCache(widgetType.bundleAlias, widgetType.alias, widgetType.tenantId.id === NULL_UUID);
- });
-
- this.widgetService.onWidgetBundleDeleted().subscribe((widgetsBundle) => {
- this.deleteWidgetsBundleFromCache(widgetsBundle.alias, widgetsBundle.tenantId.id === NULL_UUID);
- });
-
this.init();
}
@@ -223,7 +212,7 @@ export class WidgetComponentService {
}
public getInstantWidgetInfo(widget: Widget): WidgetInfo {
- const widgetInfo = this.getWidgetInfoFromCache(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
+ const widgetInfo = this.widgetService.getWidgetInfoFromCache(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
if (widgetInfo) {
return widgetInfo;
} else {
@@ -239,7 +228,7 @@ export class WidgetComponentService {
private getWidgetInfoInternal(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable {
const widgetInfoSubject = new ReplaySubject();
- const widgetInfo = this.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);
+ const widgetInfo = this.widgetService.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);
if (widgetInfo) {
widgetInfoSubject.next(widgetInfo);
widgetInfoSubject.complete();
@@ -247,7 +236,7 @@ export class WidgetComponentService {
if (this.utils.widgetEditMode) {
this.loadWidget(this.editingWidgetType, bundleAlias, isSystem, widgetInfoSubject);
} else {
- const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
+ const key = this.widgetService.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
let fetchQueue = this.widgetsInfoFetchQueue.get(key);
if (fetchQueue) {
fetchQueue.push(widgetInfoSubject);
@@ -272,7 +261,7 @@ export class WidgetComponentService {
private loadWidget(widgetType: WidgetType, bundleAlias: string, isSystem: boolean, widgetInfoSubject: Subject) {
const widgetInfo = toWidgetInfo(widgetType);
- const key = this.createWidgetInfoCacheKey(bundleAlias, widgetInfo.alias, isSystem);
+ const key = this.widgetService.createWidgetInfoCacheKey(bundleAlias, widgetInfo.alias, isSystem);
let widgetControllerDescriptor: WidgetControllerDescriptor = null;
try {
widgetControllerDescriptor = this.createWidgetControllerDescriptor(widgetInfo, key);
@@ -297,7 +286,7 @@ export class WidgetComponentService {
widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters;
widgetInfo.actionSources = widgetControllerDescriptor.actionSources;
widgetInfo.widgetTypeFunction = widgetControllerDescriptor.widgetTypeFunction;
- this.putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
+ this.widgetService.putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
if (widgetInfoSubject) {
widgetInfoSubject.next(widgetInfo);
widgetInfoSubject.complete();
@@ -331,7 +320,7 @@ export class WidgetComponentService {
(resource) => {
resourceTasks.push(
this.resources.loadResource(resource.url).pipe(
- catchError(e => of(`Failed to load widget resource: '${resource.url}'`))
+ catchError(() => of(`Failed to load widget resource: '${resource.url}'`))
)
);
}
@@ -586,34 +575,4 @@ export class WidgetComponentService {
this.widgetsInfoFetchQueue.delete(key);
}
}
-
- // Cache functions
-
- private createWidgetInfoCacheKey(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): string {
- return `${isSystem ? 'sys_' : ''}${bundleAlias}_${widgetTypeAlias}`;
- }
-
- private getWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): WidgetInfo | undefined {
- const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
- return this.widgetsInfoInMemoryCache.get(key);
- }
-
- private putWidgetInfoToCache(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) {
- const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
- this.widgetsInfoInMemoryCache.set(key, widgetInfo);
- }
-
- private deleteWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) {
- const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
- this.widgetsInfoInMemoryCache.delete(key);
- }
-
- private deleteWidgetsBundleFromCache(bundleAlias: string, isSystem: boolean) {
- const key = (isSystem ? 'sys_' : '') + bundleAlias;
- this.widgetsInfoInMemoryCache.forEach((widgetInfo, cacheKey) => {
- if (cacheKey.startsWith(key)) {
- this.widgetsInfoInMemoryCache.delete(cacheKey);
- }
- });
- }
}
diff --git a/ui-ngx/src/app/shared/models/audit-log.models.ts b/ui-ngx/src/app/shared/models/audit-log.models.ts
index fd9ca82275..22757877c6 100644
--- a/ui-ngx/src/app/shared/models/audit-log.models.ts
+++ b/ui-ngx/src/app/shared/models/audit-log.models.ts
@@ -49,6 +49,9 @@ export enum ActionType {
ALARM_CLEAR = 'ALARM_CLEAR',
ALARM_ASSIGN = 'ALARM_ASSIGN',
ALARM_UNASSIGN = 'ALARM_UNASSIGN',
+ ADDED_COMMENT = 'ADDED_COMMENT',
+ UPDATED_COMMENT = 'UPDATED_COMMENT',
+ DELETED_COMMENT = 'DELETED_COMMENT',
LOGIN = 'LOGIN',
LOGOUT = 'LOGOUT',
LOCKOUT = 'LOCKOUT',
@@ -89,6 +92,9 @@ export const actionTypeTranslations = new Map(
[ActionType.ALARM_CLEAR, 'audit-log.type-alarm-clear'],
[ActionType.ALARM_ASSIGN, 'audit-log.type-alarm-assign'],
[ActionType.ALARM_UNASSIGN, 'audit-log.type-alarm-unassign'],
+ [ActionType.ADDED_COMMENT, 'audit-log.type-added-comment'],
+ [ActionType.UPDATED_COMMENT, 'audit-log.type-updated-comment'],
+ [ActionType.DELETED_COMMENT, 'audit-log.type-deleted-comment'],
[ActionType.LOGIN, 'audit-log.type-login'],
[ActionType.LOGOUT, 'audit-log.type-logout'],
[ActionType.LOCKOUT, 'audit-log.type-lockout'],
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json
index 272fa260d1..298e789cf5 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -735,6 +735,9 @@
"type-alarm-clear": "Cleared",
"type-alarm-assign": "Assigned",
"type-alarm-unassign": "Unassigned",
+ "type-added-comment": "Added comment",
+ "type-updated-comment": "Updated comment",
+ "type-deleted-comment": "Deleted comment",
"type-login": "Login",
"type-logout": "Logout",
"type-lockout": "Lockout",