Browse Source

Merge pull request #8090 from imbeacon/feature-alarm-assigning-with-revert

Feature alarm assigning with revert
pull/8096/head
Andrew Shvayka 4 years ago
committed by GitHub
parent
commit
c4f5d43618
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      application/src/main/data/upgrade/3.4.4/schema_update.sql
  2. 71
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  3. 1
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  4. 2
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  5. 6
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
  7. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  8. 18
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  9. 5
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
  10. 2
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  11. 5
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  12. 28
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  13. 6
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  14. 11
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  15. 13
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  16. 51
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  17. 13
      application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java
  18. 5
      application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
  19. 4
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  20. 124
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
  21. 3
      application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
  22. 21
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
  23. 7
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  24. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
  25. 2
      common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java
  26. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
  27. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  28. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  29. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  30. 22
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  31. 22
      common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java
  32. 22
      common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java
  33. 22
      common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java
  34. 2
      common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java
  35. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
  36. 25
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  37. 61
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java
  38. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
  39. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java
  40. 3
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  41. 2
      common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
  42. 3
      common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java
  43. 2
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java
  44. 10
      common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java
  45. 3
      common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java
  46. 3
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java
  47. 3
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  48. 99
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  49. 2
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  50. 29
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  51. 7
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  52. 23
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java
  53. 20
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java
  54. 2
      dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java
  55. 27
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  56. 18
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  57. 24
      dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java
  58. 54
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
  59. 2
      dao/src/main/resources/sql/schema-entities.sql
  60. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java
  61. 124
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java
  62. 8
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
  63. 9
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java
  64. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
  65. 25
      ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts
  66. 2
      ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts
  67. 5
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  68. 50
      ui-ngx/src/app/shared/models/alarm.models.ts
  69. 4
      ui-ngx/src/app/shared/models/audit-log.models.ts
  70. 8
      ui-ngx/src/assets/locale/locale.constant-en_US.json

8
application/src/main/data/upgrade/3.4.4/schema_update.sql

@ -14,6 +14,14 @@
-- limitations under the License.
--
-- ALARM ASSIGN TO USER START
ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assign_ts BIGINT;
ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assignee_id UUID;
-- ALARM ASSIGN TO USER END
CREATE TABLE IF NOT EXISTS alarm_comment (
id uuid NOT NULL,
created_time bigint NOT NULL,

71
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -30,6 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
@ -41,16 +42,23 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ALARM_INFO_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ALARM_SORT_PROPERTY_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.ASSIGNEE_ID;
import static org.thingsboard.server.controller.ControllerConstants.ASSIGN_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE;
@ -79,6 +87,7 @@ public class AlarmController extends BaseController {
private static final String ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES = "ANY, ACTIVE, CLEARED, ACK, UNACK";
private static final String ALARM_QUERY_STATUS_DESCRIPTION = "A string value representing one of the AlarmStatus enumeration value";
private static final String ALARM_QUERY_STATUS_ALLOWABLE_VALUES = "ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK";
private static final String ALARM_QUERY_ASSIGNEE_DESCRIPTION = "A string value representing the assignee user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
private static final String ALARM_QUERY_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on of next alarm fields: type, severity or status";
private static final String ALARM_QUERY_START_TIME_DESCRIPTION = "The start timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'.";
private static final String ALARM_QUERY_END_TIME_DESCRIPTION = "The end timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'.";
@ -179,6 +188,44 @@ public class AlarmController extends BaseController {
tbAlarmService.clear(alarm, getCurrentUser()).get();
}
@ApiOperation(value = "Assign/Reassign Alarm (assignAlarm)",
notes = "Assign the Alarm. " +
"Once assigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_ASSIGNED' " +
"(or ALARM_REASSIGNED in case of assigning already assigned alarm) will be generated. " +
"Referencing non-existing Alarm 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}/assign/{assigneeId}", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public Alarm assignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION)
@PathVariable(ALARM_ID) String strAlarmId,
@ApiParam(value = ASSIGN_ID_PARAM_DESCRIPTION)
@PathVariable(ASSIGNEE_ID) String strAssigneeId
) throws Exception {
checkParameter(ALARM_ID, strAlarmId);
checkParameter(ASSIGNEE_ID, strAssigneeId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
UserId assigneeId = new UserId(UUID.fromString(strAssigneeId));
checkUserId(assigneeId, Operation.READ);
return tbAlarmService.assign(alarm, getCurrentUser(), assigneeId);
}
@ApiOperation(value = "Unassign Alarm (unassignAlarm)",
notes = "Unassign the Alarm. " +
"Once unassigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_UNASSIGNED' will be generated. " +
"Referencing non-existing Alarm 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}/assign", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public Alarm assignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION)
@PathVariable(ALARM_ID) String strAlarmId
) throws Exception {
checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
return tbAlarmService.unassign(alarm, getCurrentUser());
}
@ApiOperation(value = "Get Alarms (getAlarms)",
notes = "Returns a page of alarms for the selected entity. Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " +
PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE)
@ -194,6 +241,8 @@ public class AlarmController extends BaseController {
@RequestParam(required = false) String searchStatus,
@ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)
@RequestParam(required = false) String status,
@ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION)
@RequestParam(required = false) String assigneeId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@ -221,10 +270,14 @@ public class AlarmController extends BaseController {
"and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
checkEntityId(entityId, Operation.READ);
UserId assigneeUserId = null;
if (assigneeId != null) {
assigneeUserId = new UserId(UUID.fromString(assigneeId));
}
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
try {
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get());
} catch (Exception e) {
throw handleException(e);
}
@ -244,6 +297,8 @@ public class AlarmController extends BaseController {
@RequestParam(required = false) String searchStatus,
@ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)
@RequestParam(required = false) String status,
@ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION)
@RequestParam(required = false) String assigneeId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@ -267,13 +322,17 @@ public class AlarmController extends BaseController {
throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " +
"and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
UserId assigneeUserId = null;
if (assigneeId != null) {
assigneeUserId = new UserId(UUID.fromString(assigneeId));
}
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
try {
if (getCurrentUser().isCustomerUser()) {
return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get());
} else {
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, assigneeUserId, fetchOriginator)).get());
}
} catch (Exception e) {
throw handleException(e);
@ -295,7 +354,9 @@ public class AlarmController extends BaseController {
@ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)
@RequestParam(required = false) String searchStatus,
@ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)
@RequestParam(required = false) String status
@RequestParam(required = false) String status,
@ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION)
@RequestParam(required = false) String assigneeId
) throws ThingsboardException {
checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType);
@ -308,7 +369,7 @@ public class AlarmController extends BaseController {
}
checkEntityId(entityId, Operation.READ);
try {
return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus);
return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus, assigneeId);
} catch (Exception e) {
throw handleException(e);
}

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

@ -609,7 +609,6 @@ public abstract class BaseController {
throw handleException(e, false);
}
}
Device checkDeviceId(DeviceId deviceId, Operation operation) throws ThingsboardException {
try {
validateId(deviceId, "Incorrect deviceId " + deviceId);

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

@ -27,6 +27,7 @@ public class ControllerConstants {
protected static final String EDGE_ID = "edgeId";
protected static final String RPC_ID = "rpcId";
protected static final String ENTITY_ID = "entityId";
protected static final String ASSIGNEE_ID = "assigneeId";
protected static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. " +
"The result is wrapped with PageData object that allows you to iterate over result set using pagination. " +
"See the 'Model' tab of the Response Class for more details. ";
@ -44,6 +45,7 @@ public class ControllerConstants {
protected static final String USER_ID_PARAM_DESCRIPTION = "A string value representing the user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ASSET_ID_PARAM_DESCRIPTION = "A string value representing the asset id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ALARM_ID_PARAM_DESCRIPTION = "A string value representing the alarm id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String 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'";

6
application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java

@ -86,6 +86,12 @@ public class EntityActionService {
case ALARM_CLEAR:
msgType = DataConstants.ALARM_CLEAR;
break;
case ALARM_ASSIGN:
msgType = DataConstants.ALARM_ASSIGN;
break;
case ALARM_UNASSIGN:
msgType = DataConstants.ALARM_UNASSIGN;
break;
case ALARM_DELETE:
msgType = DataConstants.ALARM_DELETE;
break;

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

@ -76,7 +76,7 @@ public abstract class AbstractTbEntityService {
protected ListenableFuture<Void> removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) {
ListenableFuture<PageData<AlarmInfo>> alarmsFuture =
alarmService.findAlarms(tenantId, new AlarmQuery(entityId, new TimePageLink(Integer.MAX_VALUE), null, null, false));
alarmService.findAlarms(tenantId, new AlarmQuery(entityId, new TimePageLink(Integer.MAX_VALUE), null, null, null, false));
ListenableFuture<List<AlarmId>> alarmIdsFuture = Futures.transform(alarmsFuture, page ->
page.getData().stream().map(AlarmInfo::getId).collect(Collectors.toList()), dbExecutor);

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

@ -308,6 +308,10 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
return EdgeEventActionType.ALARM_ACK;
case ALARM_CLEAR:
return EdgeEventActionType.ALARM_CLEAR;
case ALARM_ASSIGN:
return EdgeEventActionType.ALARM_ASSIGN;
case ALARM_UNASSIGN:
return EdgeEventActionType.ALARM_UNASSIGN;
case DELETED:
return EdgeEventActionType.DELETED;
case RELATION_ADD_OR_UPDATE:

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

@ -31,6 +31,8 @@ import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import java.util.List;
@ -93,6 +95,22 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
}, MoreExecutors.directExecutor());
}
@Override
public Alarm assign(Alarm alarm, User user, UserId assigneeId) {
long assignTs = System.currentTimeMillis();
Alarm assignedAlarm = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, assignTs);
notificationEntityService.notifyCreateOrUpdateAlarm(assignedAlarm, ActionType.ALARM_ASSIGN, user);
return assignedAlarm;
}
@Override
public Alarm unassign(Alarm alarm, User user) {
long assignTs = System.currentTimeMillis();
Alarm unassignedAlarm = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), assignTs);
notificationEntityService.notifyCreateOrUpdateAlarm(unassignedAlarm, ActionType.ALARM_UNASSIGN, user);
return unassignedAlarm;
}
@Override
public Boolean delete(Alarm alarm, User user) {
TenantId tenantId = alarm.getTenantId();

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

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId;
public interface TbAlarmService {
@ -28,5 +29,9 @@ public interface TbAlarmService {
ListenableFuture<Void> clear(Alarm alarm, User user);
Alarm assign(Alarm alarm, User user, UserId assigneeId);
Alarm unassign(Alarm alarm, User user);
Boolean delete(Alarm alarm, User user);
}

2
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -556,7 +556,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
};
private void updateTenantAlarmsCustomer(TenantId tenantId, String name, AtomicLong processed) {
AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(1000), null, null, false);
AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(1000), null, null, null, false);
PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, alarmQuery);
boolean hasNext = true;
while (hasNext) {

5
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -26,6 +26,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rpc.RpcError;
@ -502,13 +503,13 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
subscriptionManagerService.onAlarmUpdate(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), Alarm.class), callback);
JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class),callback);
} else if (msg.hasAlarmDelete()) {
TbAlarmDeleteProto proto = msg.getAlarmDelete();
subscriptionManagerService.onAlarmDeleted(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), Alarm.class), callback);
JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class), callback);
} else {
throwNotHandled(msg, callback);
}

28
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -26,6 +26,7 @@ import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -292,7 +293,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
@Override
public void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) {
public void onAlarmUpdate(TenantId tenantId, EntityId entityId, AlarmInfo alarmInfo, TbCallback callback) {
onLocalAlarmSubUpdate(entityId,
s -> {
if (TbSubscriptionType.ALARMS.equals(s.getType())) {
@ -301,15 +302,15 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
return null;
}
},
s -> alarm.getCreatedTime() >= s.getTs(),
s -> alarm,
s -> alarmInfo.getCreatedTime() >= s.getTs() || alarmInfo.getAssignTs() >= s.getTs(),
s -> alarmInfo,
false
);
callback.onSuccess();
}
@Override
public void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) {
public void onAlarmDeleted(TenantId tenantId, EntityId entityId, AlarmInfo alarmInfo, TbCallback callback) {
onLocalAlarmSubUpdate(entityId,
s -> {
if (TbSubscriptionType.ALARMS.equals(s.getType())) {
@ -318,8 +319,8 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
return null;
}
},
s -> alarm.getCreatedTime() >= s.getTs(),
s -> alarm,
s -> alarmInfo.getCreatedTime() >= s.getTs(),
s -> alarmInfo,
true
);
callback.onSuccess();
@ -414,18 +415,19 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
private void onLocalAlarmSubUpdate(EntityId entityId,
Function<TbSubscription, TbAlarmsSubscription> castFunction,
Predicate<TbAlarmsSubscription> filterFunction,
Function<TbAlarmsSubscription, Alarm> processFunction, boolean deleted) {
Function<TbAlarmsSubscription, AlarmInfo> processFunction,
boolean deleted) {
Set<TbSubscription> entitySubscriptions = subscriptionsByEntityId.get(entityId);
if (entitySubscriptions != null) {
entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> {
Alarm alarm = processFunction.apply(s);
if (alarm != null) {
AlarmInfo alarmInfo = processFunction.apply(s);
if (alarmInfo != null) {
if (serviceId.equals(s.getServiceId())) {
AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, deleted);
AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarmInfo, deleted);
localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY);
} else {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId());
toCoreNotificationsProducer.send(tpi, toProto(s, alarm, deleted), null);
toCoreNotificationsProducer.send(tpi, toProto(s, alarmInfo, deleted), null);
}
}
});
@ -562,12 +564,12 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg);
}
private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, Alarm alarm, boolean deleted) {
private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, AlarmInfo alarmInfo, boolean deleted) {
TbAlarmSubscriptionUpdateProto.Builder builder = TbAlarmSubscriptionUpdateProto.newBuilder();
builder.setSessionId(subscription.getSessionId());
builder.setSubscriptionId(subscription.getSubscriptionId());
builder.setAlarm(JacksonUtil.toString(alarm));
builder.setAlarm(JacksonUtil.toString(alarmInfo));
builder.setDeleted(deleted);
ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg(

6
application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.subscription;
import org.springframework.context.ApplicationListener;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@ -42,9 +42,9 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List<String> keys, TbCallback callback);
void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
void onAlarmUpdate(TenantId tenantId, EntityId entityId, AlarmInfo alarmInfo, TbCallback callback);
void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
void onAlarmDeleted(TenantId tenantId, EntityId entityId, AlarmInfo alarmInfo, TbCallback callback);
}

11
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -20,6 +20,7 @@ import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
@ -63,10 +64,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
private final AlarmService alarmService;
@Getter
@Setter
private final LinkedHashMap<EntityId, EntityData> entitiesMap;
@Getter
@Setter
private final HashMap<AlarmId, AlarmData> alarmsMap;
private final int maxEntitiesPerAlarmSubscription;
@ -212,8 +211,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
}
private void sendWsMsg(String sessionId, AlarmSubscriptionUpdate subscriptionUpdate) {
Alarm alarm = subscriptionUpdate.getAlarm();
AlarmId alarmId = alarm.getId();
AlarmInfo alarmInfo = subscriptionUpdate.getAlarm();
AlarmId alarmId = alarmInfo.getId();
if (subscriptionUpdate.isAlarmDeleted()) {
Alarm deleted = alarmsMap.remove(alarmId);
if (deleted != null) {
@ -222,10 +221,10 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
} else {
AlarmData current = alarmsMap.get(alarmId);
boolean onCurrentPage = current != null;
boolean matchesFilter = filter(alarm);
boolean matchesFilter = filter(alarmInfo);
if (onCurrentPage) {
if (matchesFilter) {
AlarmData updated = new AlarmData(alarm, current.getOriginatorName(), current.getEntityId());
AlarmData updated = new AlarmData(alarmInfo, current.getEntityId());
updated.getLatest().putAll(current.getLatest());
alarmsMap.put(alarmId, updated);
sendWsMsg(new AlarmDataUpdate(cmdId, null, Collections.singletonList(updated), maxEntitiesPerAlarmSubscription, data.getTotalElements()));

13
application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.subscription;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
@ -189,8 +190,8 @@ public class TbSubscriptionUtils {
if (proto.getErrorCode() > 0) {
return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg());
} else {
Alarm alarm = JacksonUtil.fromString(proto.getAlarm(), Alarm.class);
return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm);
AlarmInfo alarmInfo = JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class);
return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarmInfo);
}
}
@ -316,27 +317,27 @@ public class TbSubscriptionUtils {
return entry;
}
public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, Alarm alarm) {
public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, AlarmInfo alarmInfo) {
TbAlarmUpdateProto.Builder builder = TbAlarmUpdateProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name());
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits());
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.setAlarm(JacksonUtil.toString(alarm));
builder.setAlarm(JacksonUtil.toString(alarmInfo));
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder();
msgBuilder.setAlarmUpdate(builder);
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
}
public static ToCoreMsg toAlarmDeletedProto(TenantId tenantId, EntityId entityId, Alarm alarm) {
public static ToCoreMsg toAlarmDeletedProto(TenantId tenantId, EntityId entityId, AlarmInfo alarmInfo) {
TbAlarmDeleteProto.Builder builder = TbAlarmDeleteProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name());
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits());
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.setAlarm(JacksonUtil.toString(alarm));
builder.setAlarm(JacksonUtil.toString(alarmInfo));
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder();
msgBuilder.setAlarmDelete(builder);
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();

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

@ -37,6 +37,7 @@ 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;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
@ -93,16 +94,16 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
}
@Override
public Alarm createOrUpdateAlarm(Alarm alarm) {
public AlarmInfo createOrUpdateAlarm(Alarm alarm) {
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())) {
if (oldSeverity != null && !oldSeverity.equals(result.getAlarmInfo().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())))
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm severity was updated from %s to %s", oldSeverity, result.getAlarmInfo().getSeverity())))
.build();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
}
@ -110,7 +111,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
if (result.isCreated()) {
apiUsageClient.report(alarm.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT);
}
return result.getAlarm();
return result.getAlarmInfo();
}
@Override
@ -140,6 +141,28 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return result;
}
@Override
public AlarmInfo assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs) {
AlarmOperationResult result = alarmService.assignAlarm(tenantId, alarmId, assigneeId, assignTs);
if (result.isSuccessful()) {
onAlarmUpdated(result);
} else {
log.warn("Failed to assign alarm!");
}
return result.getAlarmInfo();
}
@Override
public AlarmInfo unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs) {
AlarmOperationResult result = alarmService.unassignAlarm(tenantId, alarmId, assignTs);
if (result.isSuccessful()) {
onAlarmUpdated(result);
} else {
log.warn("Failed to unassign alarm!");
}
return result.getAlarmInfo();
}
@Override
public ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId) {
return alarmService.findAlarmByIdAsync(tenantId, alarmId);
@ -166,8 +189,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
}
@Override
public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) {
return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus);
public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId) {
return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus, assigneeId);
}
@Override
@ -182,18 +205,18 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
private void onAlarmUpdated(AlarmOperationResult result) {
wsCallBackExecutor.submit(() -> {
Alarm alarm = result.getAlarm();
TenantId tenantId = result.getAlarm().getTenantId();
AlarmInfo alarmInfo = result.getAlarmInfo();
TenantId tenantId = alarmInfo.getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY);
subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarmInfo, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm);
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarmInfo);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
}
@ -202,18 +225,18 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
private void onAlarmDeleted(AlarmOperationResult result) {
wsCallBackExecutor.submit(() -> {
Alarm alarm = result.getAlarm();
TenantId tenantId = result.getAlarm().getTenantId();
AlarmInfo alarmInfo = result.getAlarmInfo();
TenantId tenantId = alarmInfo.getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAlarmDeleted(tenantId, entityId, alarm, TbCallback.EMPTY);
subscriptionManagerService.get().onAlarmDeleted(tenantId, entityId, alarmInfo, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm);
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarmInfo);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
}

13
application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.telemetry.sub;
import lombok.Getter;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.query.AlarmData;
@ -36,15 +37,15 @@ public class AlarmSubscriptionUpdate {
@Getter
private String errorMsg;
@Getter
private Alarm alarm;
private AlarmInfo alarm;
@Getter
private boolean alarmDeleted;
public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm) {
public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarm) {
this(subscriptionId, alarm, false);
}
public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, boolean alarmDeleted) {
public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarm, boolean alarmDeleted) {
super();
this.subscriptionId = subscriptionId;
this.alarm = alarm;
@ -64,7 +65,7 @@ public class AlarmSubscriptionUpdate {
@Override
public String toString() {
return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", alarm="
+ alarm + "]";
return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg +
", alarm=" + alarm + "]";
}
}
}

5
application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java

@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId;
@ -83,8 +84,8 @@ public class AlarmsCleanUpService {
PageData<AlarmId> toRemove = alarmDao.findAlarmsIdsByEndTsBeforeAndTenantId(expirationTime, tenantId, removalBatchRequest);
toRemove.getData().forEach(alarmId -> {
relationService.deleteEntityRelations(tenantId, alarmId);
Alarm alarm = alarmService.deleteAlarm(tenantId, alarmId).getAlarm();
entityActionService.pushEntityActionToRuleEngine(alarm.getOriginator(), alarm, tenantId, null, ActionType.ALARM_DELETE, null);
AlarmInfo alarmInfo = alarmService.deleteAlarm(tenantId, alarmId).getAlarmInfo();
entityActionService.pushEntityActionToRuleEngine(alarmInfo.getOriginator(), alarmInfo, tenantId, null, ActionType.ALARM_DELETE, null);
});
totalRemoved += toRemove.getTotalElements();

4
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -155,6 +155,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected TenantId differentTenantId;
protected CustomerId differentCustomerId;
protected UserId customerUserId;
protected UserId differentCustomerUserId;
@SuppressWarnings("rawtypes")
private HttpMessageConverter mappingJackson2HttpMessageConverter;
@ -325,7 +326,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
differentCustomerUser.setEmail(DIFFERENT_CUSTOMER_USER_EMAIL);
createUserAndLogin(differentCustomerUser, DIFFERENT_CUSTOMER_USER_PASSWORD);
differentCustomerUser = createUserAndLogin(differentCustomerUser, DIFFERENT_CUSTOMER_USER_PASSWORD);
differentCustomerUserId = differentCustomerUser.getId();
}
}

124
application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java

@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
@ -348,6 +349,129 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
.andExpect(statusReason(containsString(msgErrorPermission)));
}
@Test
public void testAssignAlarm() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
long beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
}
@Test
public void testAssignAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isForbidden());
}
@Test
public void testReassignAlarm() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
long beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
logout();
loginCustomerUser();
Mockito.reset(tbClusterService, auditLogService);
beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarm.getId() + "/assign/" + customerUserId.getId()).andExpect(status().isOk());
foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(customerUserId, foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_ASSIGN);
}
@Test
public void testUnassignAlarm() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
long beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
beforeAssignmentTs = System.currentTimeMillis();
doDelete("/api/alarm/" + alarm.getId() + "/assign").andExpect(status().isOk());
foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertNull(foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGN);
}
@Test
public void testUnassignTenantAlarmViaCustomer() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
Mockito.reset(tbClusterService, auditLogService);
long beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
logout();
loginCustomerUser();
Mockito.reset(tbClusterService, auditLogService);
beforeAssignmentTs = System.currentTimeMillis();
doDelete("/api/alarm/" + alarm.getId() + "/assign").andExpect(status().isOk());
foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertNull(foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_UNASSIGN);
}
@Test
public void testFindAlarmsViaCustomerUser() throws Exception {
loginCustomerUser();

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

@ -27,6 +27,7 @@ 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.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId;
@ -81,7 +82,7 @@ public class DefaultTbAlarmServiceTest {
@Test
public void testSave() throws ThingsboardException {
var alarm = new Alarm();
var alarm = new AlarmInfo();
when(alarmSubscriptionService.createOrUpdateAlarm(alarm)).thenReturn(alarm);
service.save(alarm, new User());

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

@ -19,6 +19,7 @@ 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;
import java.util.Collections;
@ -27,17 +28,29 @@ import java.util.List;
@Data
@AllArgsConstructor
public class AlarmOperationResult {
private final Alarm alarm;
private final AlarmInfo alarmInfo;
private final boolean successful;
private final boolean created;
private final AlarmSeverity oldSeverity;
private final List<EntityId> propagatedEntitiesList;
public AlarmOperationResult(Alarm alarm, boolean successful) {
this(alarm, successful, Collections.emptyList());
this(new AlarmInfo(alarm, null, null, null, null, null), successful, Collections.emptyList());
}
public AlarmOperationResult(Alarm alarm, boolean successful, List<EntityId> propagatedEntitiesList) {
this(alarm, successful, false, null, propagatedEntitiesList);
public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful) {
this(alarmInfo, successful, Collections.emptyList());
}
public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful, List<EntityId> propagatedEntitiesList) {
this(alarmInfo, successful, false, null, propagatedEntitiesList);
}
public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful, boolean created, List<EntityId> propagatedEntitiesList) {
this.alarmInfo = alarmInfo;
this.successful = successful;
this.created = created;
this.propagatedEntitiesList = propagatedEntitiesList;
this.oldSeverity = null;
}
}

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

@ -27,6 +27,7 @@ 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;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
@ -49,6 +50,10 @@ public interface AlarmService extends EntityDaoService {
ListenableFuture<AlarmOperationResult> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
AlarmOperationResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs);
AlarmOperationResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs);
Alarm findAlarmById(TenantId tenantId, AlarmId alarmId);
ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId);
@ -60,7 +65,7 @@ public interface AlarmService extends EntityDaoService {
ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query);
AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus,
AlarmStatus alarmStatus);
AlarmStatus alarmStatus, String assigneeId);
ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java

@ -29,6 +29,8 @@ public interface EntityService {
Optional<String> fetchEntityName(TenantId tenantId, EntityId entityId);
Optional<String> fetchEntityLabel(TenantId tenantId, EntityId entityId);
Optional<CustomerId> fetchEntityCustomerId(TenantId tenantId, EntityId entityId);
long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query);

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

@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName {
public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasEmail {
private static final long serialVersionUID = 5047448057830660988L;

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

@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
public class Customer extends ContactBased<CustomerId> implements HasTenantId, ExportableEntity<CustomerId> {
public class Customer extends ContactBased<CustomerId> implements HasTenantId, ExportableEntity<CustomerId>, HasTitle {
private static final long serialVersionUID = -1599722990298929275L;

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

@ -31,7 +31,7 @@ import java.util.Objects;
import java.util.Set;
@ApiModel
public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName, HasTenantId {
public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName, HasTenantId, HasTitle {
private TenantId tenantId;
@NoXss

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

@ -74,6 +74,8 @@ public class DataConstants {
public static final String TIMESERIES_DELETED = "TIMESERIES_DELETED";
public static final String ALARM_ACK = "ALARM_ACK";
public static final String ALARM_CLEAR = "ALARM_CLEAR";
public static final String ALARM_ASSIGN = "ALARM_ASSIGN";
public static final String ALARM_UNASSIGN = "ALARM_UNASSIGN";
public static final String ALARM_DELETE = "ALARM_DELETE";
public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT";
public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT";

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

@ -40,7 +40,7 @@ import java.util.Optional;
@ApiModel
@EqualsAndHashCode(callSuper = true)
@Slf4j
public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasName, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity<DeviceId> {
public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity<DeviceId> {
private static final long serialVersionUID = 2807343040519543363L;

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

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

22
common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.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;
public interface HasEmail extends HasName {
String getEmail();
}

22
common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.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;
public interface HasLabel extends HasName {
String getLabel();
}

22
common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.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;
public interface HasTitle {
String getTitle();
}

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

@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.ota.OtaPackageType;
@Slf4j
@Data
@EqualsAndHashCode(callSuper = true)
public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackageId> implements HasName, HasTenantId {
public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackageId> implements HasName, HasTenantId, HasTitle {
private static final long serialVersionUID = 3168391583570815419L;

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

@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel
@EqualsAndHashCode(callSuper = true)
public class Tenant extends ContactBased<TenantId> implements HasTenantId {
public class Tenant extends ContactBased<TenantId> implements HasTenantId, HasTitle {
private static final long serialVersionUID = 8057243243859922101L;

25
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -30,6 +30,7 @@ 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;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@ -60,23 +61,27 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
private AlarmSeverity severity;
@ApiModelProperty(position = 9, required = true, value = "Alarm status", example = "CLEARED_UNACK")
private AlarmStatus status;
@ApiModelProperty(position = 10, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565")
@ApiModelProperty(position = 10, value = "Alarm assignee user id")
private UserId assigneeId;
@ApiModelProperty(position = 11, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565")
private long startTs;
@ApiModelProperty(position = 11, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
@ApiModelProperty(position = 12, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
private long endTs;
@ApiModelProperty(position = 12, value = "Timestamp of the alarm acknowledgement, in milliseconds", example = "1634115221948")
@ApiModelProperty(position = 13, value = "Timestamp of the alarm acknowledgement, in milliseconds", example = "1634115221948")
private long ackTs;
@ApiModelProperty(position = 13, value = "Timestamp of the alarm clearing, in milliseconds", example = "1634114528465")
@ApiModelProperty(position = 14, value = "Timestamp of the alarm clearing, in milliseconds", example = "1634114528465")
private long clearTs;
@ApiModelProperty(position = 14, value = "JSON object with alarm details")
@ApiModelProperty(position = 15, value = "Timestamp of the alarm assigning0, in milliseconds", example = "1634115928465")
private long assignTs;
@ApiModelProperty(position = 16, value = "JSON object with alarm details")
private transient JsonNode details;
@ApiModelProperty(position = 15, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true")
@ApiModelProperty(position = 17, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true")
private boolean propagate;
@ApiModelProperty(position = 16, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true")
@ApiModelProperty(position = 18, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true")
private boolean propagateToOwner;
@ApiModelProperty(position = 17, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true")
@ApiModelProperty(position = 19, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true")
private boolean propagateToTenant;
@ApiModelProperty(position = 18, value = "JSON array of relation types that should be used for propagation. " +
@ApiModelProperty(position = 20, value = "JSON array of relation types that should be used for propagation. " +
"By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " +
"This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.")
private List<String> propagateRelationTypes;
@ -98,10 +103,12 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
this.originator = alarm.getOriginator();
this.severity = alarm.getSeverity();
this.status = alarm.getStatus();
this.assigneeId = alarm.getAssigneeId();
this.startTs = alarm.getStartTs();
this.endTs = alarm.getEndTs();
this.ackTs = alarm.getAckTs();
this.clearTs = alarm.getClearTs();
this.assignTs = alarm.getAssignTs();
this.details = alarm.getDetails();
this.propagate = alarm.isPropagate();
this.propagateToOwner = alarm.isPropagateToOwner();

61
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java

@ -17,15 +17,42 @@ package org.thingsboard.server.common.data.alarm;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.User;
import java.util.Objects;
@ApiModel
public class AlarmInfo extends Alarm {
private static final long serialVersionUID = 2807343093519543363L;
@Getter
@Setter
@ApiModelProperty(position = 19, value = "Alarm originator name", example = "Thermostat")
private String originatorName;
@Getter
@Setter
@ApiModelProperty(position = 20, value = "Alarm originator label", example = "Thermostat label")
private String originatorLabel;
@Getter
@Setter
@ApiModelProperty(position = 21, value = "Alarm assignee first name")
private String assigneeFirstName;
@Getter
@Setter
@ApiModelProperty(position = 22, value = "Alarm assignee last name")
private String assigneeLastName;
@Getter
@Setter
@ApiModelProperty(position = 23, value = "Alarm assignee email")
private String assigneeEmail;
public AlarmInfo() {
super();
}
@ -34,17 +61,22 @@ public class AlarmInfo extends Alarm {
super(alarm);
}
public AlarmInfo(Alarm alarm, String originatorName) {
super(alarm);
this.originatorName = originatorName;
}
public String getOriginatorName() {
return originatorName;
public AlarmInfo(AlarmInfo alarmInfo) {
super(alarmInfo);
this.originatorName = alarmInfo.originatorName;
this.originatorLabel = alarmInfo.originatorLabel;
this.assigneeFirstName = alarmInfo.assigneeFirstName;
this.assigneeLastName = alarmInfo.assigneeLastName;
this.assigneeEmail = alarmInfo.assigneeEmail;
}
public void setOriginatorName(String originatorName) {
public AlarmInfo(Alarm alarm, String originatorName, String originatorLabel, String assigneeFirstName, String assigneeLastName, String assigneeEmail) {
super(alarm);
this.originatorName = originatorName;
this.originatorLabel = originatorLabel;
this.assigneeFirstName = assigneeFirstName;
this.assigneeLastName = assigneeLastName;
this.assigneeEmail = assigneeEmail;
}
@Override
@ -55,14 +87,21 @@ public class AlarmInfo extends Alarm {
AlarmInfo alarmInfo = (AlarmInfo) o;
return originatorName != null ? originatorName.equals(alarmInfo.originatorName) : alarmInfo.originatorName == null;
return (Objects.equals(originatorName, alarmInfo.originatorName)) &&
(Objects.equals(originatorLabel, alarmInfo.originatorLabel)) &&
(Objects.equals(assigneeFirstName, alarmInfo.assigneeFirstName)) &&
(Objects.equals(assigneeLastName, alarmInfo.assigneeLastName)) &&
(Objects.equals(assigneeEmail, alarmInfo.assigneeEmail));
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (originatorName != null ? originatorName.hashCode() : 0);
result = 31 * result + (originatorName != null ? originatorName.hashCode() : 0)
+ (originatorLabel != null ? originatorLabel.hashCode() : 0)
+ (assigneeFirstName != null ? assigneeFirstName.hashCode() : 0)
+ (assigneeLastName != null ? assigneeLastName.hashCode() : 0)
+ (assigneeEmail != null ? assigneeEmail.hashCode() : 0);
return result;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java

@ -19,6 +19,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageLink;
/**
@ -33,6 +34,7 @@ public class AlarmQuery {
private TimePageLink pageLink;
private AlarmSearchStatus searchStatus;
private AlarmStatus status;
private UserId assigneeId;
private Boolean fetchOriginator;
}

2
common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java

@ -23,6 +23,7 @@ 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;
import org.thingsboard.server.common.data.id.UserId;
@Data
@NoArgsConstructor
@ -35,6 +36,7 @@ public class EntityAlarm implements HasTenantId {
private String alarmType;
private CustomerId customerId;
private UserId assigneeId;
private AlarmId alarmId;
}

3
common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java

@ -23,6 +23,7 @@ import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasLabel;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
@ -37,7 +38,7 @@ import java.util.Optional;
@ApiModel
@EqualsAndHashCode(callSuper = true)
public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId, ExportableEntity<AssetId> {
public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasLabel, HasTenantId, HasCustomerId, ExportableEntity<AssetId> {
private static final long serialVersionUID = 2807343040519543363L;

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

@ -40,6 +40,8 @@ public enum ActionType {
ALARM_ACK(false),
ALARM_CLEAR(false),
ALARM_DELETE(false),
ALARM_ASSIGN(false),
ALARM_UNASSIGN(false),
LOGIN(false),
LOGOUT(false),
LOCKOUT(false),

3
common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java

@ -21,6 +21,7 @@ import lombok.EqualsAndHashCode;
import lombok.Setter;
import lombok.ToString;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasLabel;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
@ -35,7 +36,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
@ToString
@Setter
public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements HasName, HasTenantId, HasCustomerId {
public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements HasLabel, HasTenantId, HasCustomerId {
private static final long serialVersionUID = 4934987555236873728L;

2
common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java

@ -31,6 +31,8 @@ public enum EdgeEventActionType {
RPC_CALL,
ALARM_ACK,
ALARM_CLEAR,
ALARM_ASSIGN,
ALARM_UNASSIGN,
ASSIGNED_TO_EDGE,
UNASSIGNED_FROM_EDGE,
CREDENTIALS_REQUEST,

10
common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java

@ -31,8 +31,14 @@ public class AlarmData extends AlarmInfo {
@Getter
private final Map<EntityKeyType, Map<String, TsValue>> latest;
public AlarmData(Alarm alarm, String originatorName, EntityId entityId) {
super(alarm, originatorName);
public AlarmData(AlarmInfo alarmInfo, EntityId entityId) {
super(alarmInfo);
this.entityId = entityId;
this.latest = new HashMap<>();
}
public AlarmData(Alarm alarm, EntityId entityId) {
super(alarm);
this.entityId = entityId;
this.latest = new HashMap<>();
}

3
common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.widget;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
@ -25,7 +26,7 @@ import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasTenantId {
public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasName, HasTenantId {
private static final long serialVersionUID = 8388684344603660756L;

3
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java

@ -25,6 +25,7 @@ import lombok.Setter;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.HasTitle;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
@ -33,7 +34,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel
@EqualsAndHashCode(callSuper = true)
public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements HasName, HasTenantId, ExportableEntity<WidgetsBundleId> {
public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements HasName, HasTenantId, ExportableEntity<WidgetsBundleId>, HasTitle {
private static final long serialVersionUID = -7627368878362410489L;

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

@ -26,6 +26,7 @@ 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;
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.query.AlarmData;
@ -58,7 +59,7 @@ public interface AlarmDao extends Dao<Alarm> {
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> status);
Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> status, String assigneeId);
PageData<AlarmId> findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink);

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

@ -28,6 +28,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
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.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
@ -41,6 +42,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
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.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
@ -51,6 +53,7 @@ import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.user.UserService;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
@ -84,6 +87,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
@Autowired
private EntityService entityService;
@Autowired
private UserService userService;
@Autowired
private DataValidator<Alarm> alarmDataValidator;
@ -116,7 +122,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
if (alarm.getEndTs() == 0L) {
alarm.setEndTs(alarm.getStartTs());
}
alarm.setCustomerId(entityService.fetchEntityCustomerId(alarm.getTenantId(), alarm.getOriginator()).get());
alarm.setCustomerId(entityService.fetchEntityCustomerId(alarm.getTenantId(), alarm.getOriginator()).orElse(null));
if (alarm.getId() == null) {
Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType());
if (existing == null || existing.getStatus().isCleared()) {
@ -155,7 +161,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
if (alarm == null) {
return new AlarmOperationResult(alarm, false);
}
AlarmOperationResult result = new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)));
AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm);
AlarmOperationResult result = new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm)));
deleteEntityRelations(tenantId, alarm.getId());
alarmDao.removeById(tenantId, alarm.getUuidId());
return result;
@ -165,7 +172,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
log.debug("New Alarm : {}", alarm);
Alarm saved = alarmDao.save(alarm.getTenantId(), alarm);
List<EntityId> propagatedEntitiesList = createEntityAlarmRecords(saved);
return new AlarmOperationResult(saved, true, true, null, propagatedEntitiesList);
AlarmInfo alarmInfo = getAlarmInfo(alarm.getTenantId(), saved);
return new AlarmOperationResult(alarmInfo, true, true, propagatedEntitiesList);
}
private List<EntityId> createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException {
@ -221,7 +229,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} else {
propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result));
}
return new AlarmOperationResult(result, true, false, oldAlarmSeverity, propagatedEntitiesList);
AlarmInfo alarmInfo = getAlarmInfo(newAlarm.getTenantId(), newAlarm);
return new AlarmOperationResult(alarmInfo, true, false, oldAlarmSeverity, propagatedEntitiesList);
}
@Override
@ -238,7 +247,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
alarm.setStatus(newStatus);
alarm.setAckTs(ackTime);
alarm = alarmDao.save(alarm.getTenantId(), alarm);
return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)));
AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm);
return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm)));
}
}
});
@ -261,7 +271,46 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
alarm.setDetails(details);
}
alarm = alarmDao.save(alarm.getTenantId(), alarm);
return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)));
AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm);
return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm)));
}
}
});
}
@Override
public AlarmOperationResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTime) {
return getAndUpdate(tenantId, alarmId, new Function<>() {
@Nullable
@Override
public AlarmOperationResult apply(@Nullable Alarm alarm) {
if (alarm == null || assigneeId.equals(alarm.getAssigneeId())) {
return new AlarmOperationResult(alarm, false);
} else {
alarm.setAssigneeId(assigneeId);
alarm.setAssignTs(assignTime);
alarm = alarmDao.save(alarm.getTenantId(), alarm);
AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm);
return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm)));
}
}
});
}
@Override
public AlarmOperationResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTime) {
return getAndUpdate(tenantId, alarmId, new Function<>() {
@Nullable
@Override
public AlarmOperationResult apply(@Nullable Alarm alarm) {
if (alarm == null || alarm.getAssigneeId() == null) {
return new AlarmOperationResult(alarm, false);
} else {
alarm.setAssigneeId(null);
alarm.setAssignTs(assignTime);
alarm = alarmDao.save(alarm.getTenantId(), alarm);
AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm);
return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm)));
}
}
});
@ -286,12 +335,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
log.trace("Executing findAlarmInfoByIdAsync [{}]", alarmId);
validateId(alarmId, "Incorrect alarmId " + alarmId);
return Futures.transform(alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()),
a -> {
AlarmInfo alarmInfo = new AlarmInfo(a);
alarmInfo.setOriginatorName(
entityService.fetchEntityName(tenantId, alarmInfo.getOriginator()).orElse("N/A"));
return alarmInfo;
}, MoreExecutors.directExecutor());
a -> getAlarmInfo(tenantId, a), MoreExecutors.directExecutor());
}
@Override
@ -317,6 +361,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
for (AlarmInfo alarmInfo : alarms.getData()) {
alarmInfo.setOriginatorName(
entityService.fetchEntityName(tenantId, alarmInfo.getOriginator()).orElse("Deleted"));
alarmInfo.setOriginatorLabel(
entityService.fetchEntityLabel(tenantId, alarmInfo.getOriginator()).orElse(null));
alarmFutures.add(Futures.immediateFuture(alarmInfo));
}
return Futures.transform(Futures.successfulAsList(alarmFutures),
@ -326,7 +372,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
@Override
public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus,
AlarmStatus alarmStatus) {
AlarmStatus alarmStatus, String assigneeId) {
Set<AlarmStatus> statusList = null;
if (alarmSearchStatus != null) {
statusList = alarmSearchStatus.getStatuses();
@ -334,7 +380,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
statusList = Collections.singleton(alarmStatus);
}
Set<AlarmSeverity> alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList);
Set<AlarmSeverity> alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList, assigneeId);
return alarmSeverities.stream().min(AlarmSeverity::compareTo).orElse(null);
}
@ -357,10 +403,14 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
if (alarm.getAckTs() > existing.getAckTs()) {
existing.setAckTs(alarm.getAckTs());
}
if (alarm.getAssignTs() > existing.getAssignTs()) {
existing.setAssignTs(alarm.getAssignTs());
}
existing.setStatus(alarm.getStatus());
existing.setSeverity(alarm.getSeverity());
existing.setDetails(alarm.getDetails());
existing.setCustomerId(alarm.getCustomerId());
existing.setAssigneeId(alarm.getAssigneeId());
existing.setPropagate(existing.isPropagate() || alarm.isPropagate());
existing.setPropagateToOwner(existing.isPropagateToOwner() || alarm.isPropagateToOwner());
existing.setPropagateToTenant(existing.isPropagateToTenant() || alarm.isPropagateToTenant());
@ -388,7 +438,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
private void createEntityAlarmRecord(TenantId tenantId, EntityId entityId, Alarm alarm) {
EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), alarm.getId());
EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), null, alarm.getId());
try {
alarmDao.createEntityAlarmRecord(entityAlarm);
} catch (Exception e) {
@ -408,6 +458,25 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return function.apply(entity);
}
private AlarmInfo getAlarmInfo(TenantId tenantId, Alarm alarm) {
String originatorName;
String originatorLabel;
String assigneeFirstName = null;
String assigneeLastName = null;
String assigneeEmail = null;
originatorName = entityService.fetchEntityName(tenantId, alarm.getOriginator()).orElse("Deleted");
originatorLabel = entityService.fetchEntityLabel(tenantId, alarm.getOriginator()).orElse(null);
if (alarm.getAssigneeId() != null) {
User assignedUser = userService.findUserById(tenantId, alarm.getAssigneeId());
assigneeFirstName = assignedUser.getFirstName();
assigneeLastName = assignedUser.getLastName();
assigneeEmail = assignedUser.getEmail();
}
return new AlarmInfo(alarm, originatorName, originatorLabel, assigneeFirstName, assigneeLastName, assigneeEmail);
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findAlarmById(tenantId, new AlarmId(entityId.getId())));

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

@ -166,6 +166,8 @@ public class AuditLogServiceImpl implements AuditLogService {
case UPDATED:
case ALARM_ACK:
case ALARM_CLEAR:
case ALARM_ASSIGN:
case ALARM_UNASSIGN:
case RELATIONS_DELETED:
case ASSIGNED_TO_TENANT:
if (entity != null) {

29
dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

@ -20,7 +20,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasEmail;
import org.thingsboard.server.common.data.HasLabel;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTitle;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
@ -89,6 +92,30 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
return Optional.empty();
}
@Override
public Optional<String> fetchEntityLabel(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchEntityLabel [{}]", entityId);
EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType());
Optional<HasId<?>> entityOpt = entityDaoService.findEntity(tenantId, entityId);
String entityLabel = null;
if (entityOpt.isPresent()) {
HasId<?> entity = entityOpt.get();
if (entity instanceof HasTitle) {
entityLabel = ((HasTitle) entity).getTitle();
}
if (entity instanceof HasLabel && entityLabel == null) {
entityLabel = ((HasLabel) entity).getLabel();
}
if (entity instanceof HasEmail && entityLabel == null) {
entityLabel = ((HasEmail) entity).getEmail();
}
if (entity instanceof HasName && entityLabel == null) {
entityLabel = ((HasName) entity).getName();
}
}
return Optional.ofNullable(entityLabel);
}
@Override
public Optional<CustomerId> fetchEntityCustomerId(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchEntityCustomerId [{}]", entityId);
@ -126,7 +153,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
}
private static void validateRelationQuery(RelationsQueryFilter queryFilter) {
if (queryFilter.isMultiRoot() && queryFilter.getMultiRootEntitiesType() ==null){
if (queryFilter.isMultiRoot() && queryFilter.getMultiRootEntitiesType() == null) {
throw new IncorrectParameterException("Multi-root relation query filter should contain 'multiRootEntitiesType'");
}
if (queryFilter.isMultiRoot() && CollectionUtils.isEmpty(queryFilter.getMultiRootEntityIds())) {

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

@ -41,6 +41,7 @@ public class ModelConstants {
public static final String USER_ID_PROPERTY = "user_id";
public static final String TENANT_ID_PROPERTY = "tenant_id";
public static final String CUSTOMER_ID_PROPERTY = "customer_id";
public static final String ASSIGNEE_ID_PROPERTY = "assignee_id";
public static final String DEVICE_ID_PROPERTY = "device_id";
public static final String TITLE_PROPERTY = "title";
public static final String ALIAS_PROPERTY = "alias";
@ -292,13 +293,19 @@ public class ModelConstants {
public static final String ALARM_DETAILS_PROPERTY = "details";
public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id";
public static final String ALARM_ORIGINATOR_NAME_PROPERTY = "originator_name";
public static final String ALARM_ORIGINATOR_LABEL_PROPERTY = "originator_label";
public static final String ALARM_ORIGINATOR_TYPE_PROPERTY = "originator_type";
public static final String ALARM_SEVERITY_PROPERTY = "severity";
public static final String ALARM_STATUS_PROPERTY = "status";
public static final String ALARM_ASSIGNEE_ID_PROPERTY = "assignee_id";
public static final String ALARM_ASSIGNEE_FIRST_NAME_PROPERTY = "assignee_first_name";
public static final String ALARM_ASSIGNEE_LAST_NAME_PROPERTY = "assignee_last_name";
public static final String ALARM_ASSIGNEE_EMAIL_PROPERTY = "assignee_email";
public static final String ALARM_START_TS_PROPERTY = "start_ts";
public static final String ALARM_END_TS_PROPERTY = "end_ts";
public static final String ALARM_ACK_TS_PROPERTY = "ack_ts";
public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts";
public static final String ALARM_ASSIGN_TS_PROPERTY = "assign_ts";
public static final String ALARM_PROPAGATE_PROPERTY = "propagate";
public static final String ALARM_PROPAGATE_TO_OWNER_PROPERTY = "propagate_to_owner";
public static final String ALARM_PROPAGATE_TO_TENANT_PROPERTY = "propagate_to_tenant";

23
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java

@ -20,6 +20,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.springframework.data.annotation.Id;
import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
@ -30,6 +31,7 @@ import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
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;
@ -44,6 +46,8 @@ import java.util.Collections;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGN_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CUSTOMER_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY;
@ -88,6 +92,10 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
@Column(name = ALARM_STATUS_PROPERTY)
private AlarmStatus status;
@Type(type="pg-uuid")
@Column(name = ALARM_ASSIGNEE_ID_PROPERTY)
private UUID assigneeId;
@Column(name = ALARM_START_TS_PROPERTY)
private Long startTs;
@ -100,6 +108,9 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
@Column(name = ALARM_CLEAR_TS_PROPERTY)
private Long clearTs;
@Column(name = ALARM_ASSIGN_TS_PROPERTY)
private Long assignTs;
@Type(type = "json")
@Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY)
private JsonNode details;
@ -137,6 +148,9 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.type = alarm.getType();
this.severity = alarm.getSeverity();
this.status = alarm.getStatus();
if (alarm.getAssigneeId() != null) {
this.assigneeId = alarm.getAssigneeId().getId();
}
this.propagate = alarm.isPropagate();
this.propagateToOwner = alarm.isPropagateToOwner();
this.propagateToTenant = alarm.isPropagateToTenant();
@ -144,11 +158,12 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.endTs = alarm.getEndTs();
this.ackTs = alarm.getAckTs();
this.clearTs = alarm.getClearTs();
this.assignTs = alarm.getAssignTs();
this.details = alarm.getDetails();
if (!CollectionUtils.isEmpty(alarm.getPropagateRelationTypes())) {
this.propagateRelationTypes = String.join(",", alarm.getPropagateRelationTypes());
} else {
this.propagateRelationTypes = null;
this.propagateRelationTypes = "";
}
}
@ -163,6 +178,7 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.type = alarmEntity.getType();
this.severity = alarmEntity.getSeverity();
this.status = alarmEntity.getStatus();
this.assigneeId = alarmEntity.getAssigneeId();
this.propagate = alarmEntity.getPropagate();
this.propagateToOwner = alarmEntity.getPropagateToOwner();
this.propagateToTenant = alarmEntity.getPropagateToTenant();
@ -170,6 +186,7 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.endTs = alarmEntity.getEndTs();
this.ackTs = alarmEntity.getAckTs();
this.clearTs = alarmEntity.getClearTs();
this.assignTs = alarmEntity.getAssignTs();
this.details = alarmEntity.getDetails();
this.propagateRelationTypes = alarmEntity.getPropagateRelationTypes();
}
@ -187,6 +204,9 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
alarm.setType(type);
alarm.setSeverity(severity);
alarm.setStatus(status);
if (assigneeId != null) {
alarm.setAssigneeId(new UserId(assigneeId));
}
alarm.setPropagate(propagate);
alarm.setPropagateToOwner(propagateToOwner);
alarm.setPropagateToTenant(propagateToTenant);
@ -194,6 +214,7 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
alarm.setEndTs(endTs);
alarm.setAckTs(ackTs);
alarm.setClearTs(clearTs);
alarm.setAssignTs(assignTs);
alarm.setDetails(details);
if (!StringUtils.isEmpty(propagateRelationTypes)) {
alarm.setPropagateRelationTypes(Arrays.asList(propagateRelationTypes.split(",")));

20
dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java

@ -24,17 +24,33 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
public class AlarmInfoEntity extends AbstractAlarmEntity<AlarmInfo> {
private String originatorName;
private String originatorLabel;
private String assigneeFirstName;
private String assigneeLastName;
private String assigneeEmail;
public AlarmInfoEntity() {
super();
}
public AlarmInfoEntity(AlarmEntity alarmEntity) {
public AlarmInfoEntity(AlarmEntity alarmEntity, String assigneeFirstName,
String assigneeLastName, String assigneeEmail) {
super(alarmEntity);
this.assigneeFirstName = assigneeFirstName;
this.assigneeLastName = assigneeLastName;
this.assigneeEmail = assigneeEmail;
}
@Override
public AlarmInfo toData() {
return new AlarmInfo(super.toAlarm(), this.originatorName);
AlarmInfo alarmInfo = new AlarmInfo(super.toAlarm());
alarmInfo.setOriginatorName(originatorName);
alarmInfo.setOriginatorLabel(originatorLabel);
alarmInfo.setAssigneeFirstName(assigneeFirstName);
alarmInfo.setAssigneeLastName(assigneeLastName);
alarmInfo.setAssigneeEmail(assigneeEmail);
return alarmInfo;
}
}

2
dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.model.ToData;
import javax.persistence.Column;
@ -30,6 +31,7 @@ import javax.persistence.IdClass;
import javax.persistence.Table;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ASSIGNEE_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.CREATED_TIME_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ALARM_COLUMN_FAMILY_NAME;

27
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java

@ -39,8 +39,10 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Param("alarmType") String alarmType,
Pageable pageable);
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " +
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a, tbu.firstName, tbu.lastName, tbu.email) " +
"FROM AlarmEntity a " +
"LEFT JOIN EntityAlarmEntity ea ON a.id = ea.alarmId " +
"LEFT JOIN UserEntity tbu ON tbu.id = a.assigneeId " +
"WHERE a.tenantId = :tenantId " +
"AND ea.tenantId = :tenantId " +
"AND ea.entityId = :affectedEntityId " +
@ -48,6 +50,7 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " +
"AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) "
@ -63,6 +66,7 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " +
"AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ")
@ -72,14 +76,18 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
@Param("alarmStatuses") Set<AlarmStatus> alarmStatuses,
@Param("assigneeId") String assigneeId,
@Param("searchText") String searchText,
Pageable pageable);
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " +
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a, tbu.firstName, tbu.lastName, tbu.email) " +
"FROM AlarmEntity a " +
"LEFT JOIN UserEntity tbu ON tbu.id = a.assigneeId " +
"WHERE a.tenantId = :tenantId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ",
@ -90,6 +98,7 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ")
@ -97,14 +106,18 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
@Param("alarmStatuses") Set<AlarmStatus> alarmStatuses,
@Param("assigneeId") String assigneeId,
@Param("searchText") String searchText,
Pageable pageable);
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " +
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a, tbu.firstName, tbu.lastName, tbu.email) " +
"FROM AlarmEntity a " +
"LEFT JOIN UserEntity tbu ON tbu.id = a.assigneeId " +
"WHERE a.tenantId = :tenantId AND a.customerId = :customerId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) "
@ -116,6 +129,7 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) ")
@ -124,6 +138,7 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
@Param("alarmStatuses") Set<AlarmStatus> alarmStatuses,
@Param("assigneeId") String assigneeId,
@Param("searchText") String searchText,
Pageable pageable);
@ -133,11 +148,13 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"AND ea.tenantId = :tenantId " +
"AND ea.entityId = :affectedEntityId " +
"AND ea.entityType = :affectedEntityType " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses))")
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId))")
Set<AlarmSeverity> findAlarmSeverities(@Param("tenantId") UUID tenantId,
@Param("affectedEntityId") UUID affectedEntityId,
@Param("affectedEntityType") String affectedEntityType,
@Param("alarmStatuses") Set<AlarmStatus> alarmStatuses);
@Param("alarmStatuses") Set<AlarmStatus> alarmStatuses,
@Param("assigneeId") String assigneeId);
@Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.createdTime < :time AND a.endTs < :time")
Page<UUID> findAlarmsIdsByEndTsBeforeAndTenantId(@Param("time") Long time, @Param("tenantId") UUID tenantId, Pageable pageable);

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

@ -17,6 +17,8 @@ package org.thingsboard.server.dao.sql.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.UUIDCharType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
@ -32,6 +34,7 @@ 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;
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.query.AlarmData;
@ -112,6 +115,10 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
} else if (query.getStatus() != null) {
statusSet = Collections.singleton(query.getStatus());
}
String assigneeId = null;
if (query.getAssigneeId() != null) {
assigneeId = query.getAssigneeId().toString();
}
if (affectedEntity != null) {
return DaoUtil.toPageData(
alarmRepository.findAlarms(
@ -121,6 +128,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
query.getPageLink().getStartTime(),
query.getPageLink().getEndTime(),
statusSet,
assigneeId,
Objects.toString(query.getPageLink().getTextSearch(), ""),
DaoUtil.toPageable(query.getPageLink())
)
@ -132,6 +140,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
query.getPageLink().getStartTime(),
query.getPageLink().getEndTime(),
statusSet,
assigneeId,
Objects.toString(query.getPageLink().getTextSearch(), ""),
DaoUtil.toPageable(query.getPageLink())
)
@ -148,6 +157,10 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
} else if (query.getStatus() != null) {
statusSet = Collections.singleton(query.getStatus());
}
String assigneeId = null;
if (query.getAssigneeId() != null) {
assigneeId = query.getAssigneeId().toString();
}
return DaoUtil.toPageData(
alarmRepository.findCustomerAlarms(
tenantId.getId(),
@ -155,6 +168,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
query.getPageLink().getStartTime(),
query.getPageLink().getEndTime(),
statusSet,
assigneeId,
Objects.toString(query.getPageLink().getTextSearch(), ""),
DaoUtil.toPageable(query.getPageLink())
)
@ -167,8 +181,8 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
}
@Override
public Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> statuses) {
return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses);
public Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> statuses, String assigneeId) {
return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses, assigneeId);
}
@Override

24
dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
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.query.AlarmData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
@ -68,6 +69,7 @@ public class AlarmDataAdapter {
alarm.setCreatedTime((long) row.get(ModelConstants.CREATED_TIME_PROPERTY));
alarm.setAckTs((long) row.get(ModelConstants.ALARM_ACK_TS_PROPERTY));
alarm.setClearTs((long) row.get(ModelConstants.ALARM_CLEAR_TS_PROPERTY));
alarm.setAssignTs((long) row.get(ModelConstants.ALARM_ASSIGN_TS_PROPERTY));
alarm.setStartTs((long) row.get(ModelConstants.ALARM_START_TS_PROPERTY));
alarm.setEndTs((long) row.get(ModelConstants.ALARM_END_TS_PROPERTY));
Object additionalInfo = row.get(ModelConstants.ADDITIONAL_INFO_PROPERTY);
@ -81,6 +83,16 @@ public class AlarmDataAdapter {
EntityType originatorType = EntityType.values()[(int) row.get(ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY)];
UUID originatorId = (UUID) row.get(ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY);
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId));
Object assigneeIdObj = row.get(ModelConstants.ASSIGNEE_ID_PROPERTY);
String assigneeFirstName = null;
String assigneeLastName = null;
String assigneeEmail = null;
if (assigneeIdObj != null) {
alarm.setAssigneeId(new UserId((UUID) row.get(ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY)));
assigneeFirstName = (String) row.get(ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY);
assigneeLastName = (String) row.get(ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY);
assigneeEmail = (String) row.get(ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY);
}
alarm.setPropagate((boolean) row.get(ModelConstants.ALARM_PROPAGATE_PROPERTY));
alarm.setPropagateToOwner((boolean) row.get(ModelConstants.ALARM_PROPAGATE_TO_OWNER_PROPERTY));
alarm.setPropagateToTenant((boolean) row.get(ModelConstants.ALARM_PROPAGATE_TO_TENANT_PROPERTY));
@ -105,7 +117,17 @@ public class AlarmDataAdapter {
EntityId entityId = entityIdMap.get(entityUuid);
Object originatorNameObj = row.get(ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY);
String originatorName = originatorNameObj != null ? originatorNameObj.toString() : null;
return new AlarmData(alarm, originatorName, entityId);
Object originatorLabelObj = row.get(ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY);
String originatorLabel = originatorLabelObj != null ? originatorLabelObj.toString() : null;
AlarmData alarmData = new AlarmData(alarm, entityId);
alarmData.setOriginatorName(originatorName);
alarmData.setOriginatorLabel(originatorLabel);
alarmData.setAssigneeFirstName(assigneeFirstName);
alarmData.setAssigneeLastName(assigneeLastName);
alarmData.setAssigneeEmail(assigneeEmail);
return alarmData;
}
}

54
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java

@ -57,6 +57,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
alarmFieldColumnMap.put("ackTime", ModelConstants.ALARM_ACK_TS_PROPERTY);
alarmFieldColumnMap.put("clearTs", ModelConstants.ALARM_CLEAR_TS_PROPERTY);
alarmFieldColumnMap.put("clearTime", ModelConstants.ALARM_CLEAR_TS_PROPERTY);
alarmFieldColumnMap.put("assignTime", ModelConstants.ALARM_ASSIGN_TS_PROPERTY);
alarmFieldColumnMap.put("details", ModelConstants.ADDITIONAL_INFO_PROPERTY);
alarmFieldColumnMap.put("endTs", ModelConstants.ALARM_END_TS_PROPERTY);
alarmFieldColumnMap.put("endTime", ModelConstants.ALARM_END_TS_PROPERTY);
@ -67,7 +68,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
alarmFieldColumnMap.put("severity", ModelConstants.ALARM_SEVERITY_PROPERTY);
alarmFieldColumnMap.put("originatorId", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY);
alarmFieldColumnMap.put("originatorType", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY);
alarmFieldColumnMap.put("originator", "originator_name");
alarmFieldColumnMap.put("assigneeId", ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY);
alarmFieldColumnMap.put("originator", ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY);
alarmFieldColumnMap.put("originatorLabel", ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY);
alarmFieldColumnMap.put("assigneeFirstName", ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY);
alarmFieldColumnMap.put("assigneeLastName", ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY);
alarmFieldColumnMap.put("assigneeEmail", ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY);
}
private static final String SELECT_ORIGINATOR_NAME = " COALESCE(CASE" +
@ -85,12 +91,47 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
" THEN (select name from device where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.ENTITY_VIEW.ordinal() +
" THEN (select name from entity_view where id = a.originator_id)" +
" END, 'Deleted') as originator_name";
" WHEN a.originator_type = " + EntityType.DEVICE_PROFILE.ordinal() +
" THEN (select name from device_profile where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.ASSET_PROFILE.ordinal() +
" THEN (select name from asset_profile where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.EDGE.ordinal() +
" THEN (select name from edge where id = a.originator_id)" +
" END, 'Deleted') as " + ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY;
private static final String SELECT_ORIGINATOR_LABEL = " COALESCE(CASE" +
" WHEN a.originator_type = " + EntityType.TENANT.ordinal() +
" THEN (select title from tenant where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.CUSTOMER.ordinal() +
" THEN (select COALESCE(title, email) from customer where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.USER.ordinal() +
" THEN (select email from tb_user where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.DASHBOARD.ordinal() +
" THEN (select title from dashboard where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.ASSET.ordinal() +
" THEN (select COALESCE(label, name) from asset where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.DEVICE.ordinal() +
" THEN (select COALESCE(label, name) from device where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.ENTITY_VIEW.ordinal() +
" THEN (select name from entity_view where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.DEVICE_PROFILE.ordinal() +
" THEN (select name from device_profile where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.ASSET_PROFILE.ordinal() +
" THEN (select name from asset_profile where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.EDGE.ordinal() +
" THEN (select COALESCE(label, name) from edge where id = a.originator_id)" +
" END, 'Deleted') as " + ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY;
private static final String SELECT_ASSIGNEE_INFO = " tbu.first_name as assignee_first_name," +
" tbu.last_name as assignee_last_name," +
" tbu.email as assignee_email";
private static final String FIELDS_SELECTION = "select a.id as id," +
" a.created_time as created_time," +
" a.ack_ts as ack_ts," +
" a.clear_ts as clear_ts," +
" a.assign_ts as assign_ts," +
" a.assignee_id as assignee_id," +
" a.additional_info as additional_info," +
" a.end_ts as end_ts," +
" a.originator_id as originator_id," +
@ -104,9 +145,13 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
" a.tenant_id as tenant_id, " +
" a.customer_id as customer_id, " +
" a.propagate_relation_types as propagate_relation_types, " +
" a.type as type," + SELECT_ORIGINATOR_NAME + ", ";
" a.type as type, " +
SELECT_ORIGINATOR_NAME + ", " +
SELECT_ORIGINATOR_LABEL + ", " +
SELECT_ASSIGNEE_INFO + ", ";
private static final String JOIN_ENTITY_ALARMS = "inner join entity_alarm ea on a.id = ea.alarm_id";
private static final String JOIN_ENTITY_ALARMS = "inner join entity_alarm ea on a.id = ea.alarm_id ";
private static final String LEFT_JOIN_TB_USERS = "left join tb_user tbu on a.assignee_id = tbu.id ";
protected final NamedParameterJdbcTemplate jdbcTemplate;
private final TransactionTemplate transactionTemplate;
@ -139,6 +184,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
} else {
selectPart.append(" a.originator_id as entity_id ");
}
fromPart.append(LEFT_JOIN_TB_USERS);
EntityDataSortOrder sortOrder = pageLink.getSortOrder();
String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch());
if (sortOrder != null && sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) {

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

@ -52,7 +52,9 @@ CREATE TABLE IF NOT EXISTS alarm (
propagate boolean,
severity varchar(255),
start_ts bigint,
assign_ts bigint,
status varchar(255),
assignee_id uuid,
tenant_id uuid,
customer_id uuid,
propagate_relation_types varchar,

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

@ -60,7 +60,7 @@ public abstract class BaseAlarmCommentServiceTest extends AbstractServiceTest {
.type(TEST_ALARM)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(System.currentTimeMillis()).build();
alarm = alarmService.createOrUpdateAlarm(alarm).getAlarm();
alarm = new Alarm(alarmService.createOrUpdateAlarm(alarm).getAlarmInfo());
user = new User();
user.setAuthority(Authority.TENANT_ADMIN);

124
dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java

@ -20,9 +20,11 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
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.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
@ -31,7 +33,6 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.SortOrder;
@ -45,8 +46,8 @@ import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.common.util.JacksonUtil;
import java.util.Arrays;
import java.util.Collections;
@ -56,6 +57,11 @@ import java.util.concurrent.ExecutionException;
public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
public static final String TEST_ALARM = "TEST_ALARM";
private static final String TEST_TENANT_EMAIL = "testtenant@thingsboard.org";
private static final String TEST_TENANT_FIRST_NAME = "testtenantfirstname";
private static final String TEST_TENANT_LAST_NAME = "testtenantlastname";
private TenantId tenantId;
@Before
@ -89,7 +95,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm);
Alarm created = result.getAlarm();
Alarm created = new Alarm(result.getAlarmInfo());
Assert.assertNotNull(created);
Assert.assertNotNull(created.getId());
@ -128,7 +134,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm);
Alarm created = result.getAlarm();
Alarm created = new Alarm(result.getAlarmInfo());
// Check child relation
PageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -139,7 +145,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
// Check parent relation
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -153,7 +159,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
created.setPropagate(true);
result = alarmService.createOrUpdateAlarm(created);
created = result.getAlarm();
created = new Alarm(result.getAlarmInfo());
// Check child relation
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -164,7 +170,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
// Check parent relation
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -175,7 +181,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
alarmService.ackAlarm(tenantId, created.getId(), System.currentTimeMillis()).get();
created = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get();
@ -188,7 +194,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
// Check not existing relation
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -211,6 +217,50 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
}
@Test
public void testFindAssignedAlarm() throws ExecutionException, InterruptedException {
AssetId parentId = new AssetId(Uuids.timeBased());
AssetId childId = new AssetId(Uuids.timeBased());
EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
Assert.assertTrue(relationService.saveRelationAsync(tenantId, relation).get());
long ts = System.currentTimeMillis();
Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId)
.type(TEST_ALARM)
.propagate(false)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm);
Alarm created = new Alarm(result.getAlarmInfo());
User tenantUser = new User();
tenantUser.setTenantId(tenantId);
tenantUser.setAuthority(Authority.TENANT_ADMIN);
tenantUser.setEmail(TEST_TENANT_EMAIL);
tenantUser.setFirstName(TEST_TENANT_FIRST_NAME);
tenantUser.setLastName(TEST_TENANT_LAST_NAME);
tenantUser = userService.saveUser(tenantUser);
Assert.assertNotNull(tenantUser);
AlarmOperationResult assignmentResult = alarmService.assignAlarm(tenantId, created.getId(), tenantUser.getId(), ts);
created = assignmentResult.getAlarmInfo();
PageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.assigneeId(tenantUser.getId())
.fetchOriginator(true)
.pageLink(new TimePageLink(1, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
}
@ -242,7 +292,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm);
tenantAlarm = result.getAlarm();
tenantAlarm = new Alarm(result.getAlarmInfo());
Alarm deviceAlarm = Alarm.builder().tenantId(tenantId)
.originator(customerDevice.getId())
@ -251,7 +301,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
result = alarmService.createOrUpdateAlarm(deviceAlarm);
deviceAlarm = result.getAlarm();
deviceAlarm = new Alarm(result.getAlarmInfo());
AlarmDataPageLink pageLink = new AlarmDataPageLink();
pageLink.setPage(0);
@ -279,7 +329,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(tenantAlarm, alarms.getData().get(0));
Assert.assertEquals(tenantAlarm, new Alarm(alarms.getData().get(0)));
}
@Test
@ -318,7 +368,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm);
tenantAlarm = result.getAlarm();
tenantAlarm = new Alarm(result.getAlarmInfo());
Alarm customerAlarm = Alarm.builder().tenantId(tenantId)
.originator(tenantDevice.getId())
@ -327,7 +377,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
result = alarmService.createOrUpdateAlarm(customerAlarm);
customerAlarm = result.getAlarm();
customerAlarm = new Alarm(result.getAlarmInfo());
AlarmDataPageLink pageLink = new AlarmDataPageLink();
pageLink.setPage(0);
@ -368,7 +418,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm);
tenantAlarm = result.getAlarm();
tenantAlarm = new Alarm(result.getAlarmInfo());
Alarm customerAlarm = Alarm.builder().tenantId(tenantId)
.originator(device.getId())
@ -378,7 +428,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
result = alarmService.createOrUpdateAlarm(customerAlarm);
customerAlarm = result.getAlarm();
customerAlarm = new Alarm(result.getAlarmInfo());
AlarmDataPageLink pageLink = new AlarmDataPageLink();
pageLink.setPage(0);
@ -402,11 +452,11 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(customerAlarm, customerAlarms.getData().get(0));
}
private AlarmDataQuery toQuery(AlarmDataPageLink pageLink){
private AlarmDataQuery toQuery(AlarmDataPageLink pageLink) {
return toQuery(pageLink, Collections.emptyList());
}
private AlarmDataQuery toQuery(AlarmDataPageLink pageLink, List<EntityKey> alarmFields){
private AlarmDataQuery toQuery(AlarmDataPageLink pageLink, List<EntityKey> alarmFields) {
return new AlarmDataQuery(new DeviceTypeFilter(), pageLink, null, null, null, alarmFields);
}
@ -425,7 +475,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
customerDevice = deviceService.saveDevice(customerDevice);
// no one alarms was created
Assert.assertNull(alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null));
Assert.assertNull(alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null, null));
Alarm alarm1 = Alarm.builder()
.tenantId(tenantId)
@ -435,7 +485,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.status(AlarmStatus.ACTIVE_UNACK)
.startTs(System.currentTimeMillis())
.build();
alarm1 = alarmService.createOrUpdateAlarm(alarm1).getAlarm();
alarm1 = alarmService.createOrUpdateAlarm(alarm1).getAlarmInfo();
alarmService.clearAlarm(tenantId, alarm1.getId(), null, System.currentTimeMillis()).get();
Alarm alarm2 = Alarm.builder()
@ -446,7 +496,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.status(AlarmStatus.ACTIVE_ACK)
.startTs(System.currentTimeMillis())
.build();
alarm2 = alarmService.createOrUpdateAlarm(alarm2).getAlarm();
alarm2 = alarmService.createOrUpdateAlarm(alarm2).getAlarmInfo();
alarmService.clearAlarm(tenantId, alarm2.getId(), null, System.currentTimeMillis()).get();
Alarm alarm3 = Alarm.builder()
@ -457,13 +507,13 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.status(AlarmStatus.ACTIVE_ACK)
.startTs(System.currentTimeMillis())
.build();
alarm3 = alarmService.createOrUpdateAlarm(alarm3).getAlarm();
alarm3 = alarmService.createOrUpdateAlarm(alarm3).getAlarmInfo();
Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.UNACK, null));
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null));
Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_UNACK));
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.ACTIVE, null));
Assert.assertEquals(AlarmSeverity.MINOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_ACK));
Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.UNACK, null, null));
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null, null));
Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_UNACK, null));
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.ACTIVE, null, null));
Assert.assertEquals(AlarmSeverity.MINOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_ACK, null));
}
@Test
@ -487,7 +537,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm);
Alarm created = result.getAlarm();
Alarm created = new Alarm(result.getAlarmInfo());
AlarmDataPageLink pageLink = new AlarmDataPageLink();
pageLink.setPage(0);
@ -504,7 +554,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
pageLink.setPage(0);
pageLink.setPageSize(10);
@ -530,7 +580,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
// Check child relation
created.setPropagate(true);
result = alarmService.createOrUpdateAlarm(created);
created = result.getAlarm();
created = new Alarm(result.getAlarmInfo());
// Check child relation
pageLink.setPage(0);
@ -546,7 +596,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
// Check parent relation
pageLink.setPage(0);
@ -562,7 +612,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(parentId));
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
PageData<AlarmInfo> alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(childId)
@ -607,7 +657,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(parentId));
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
alarmService.ackAlarm(tenantId, created.getId(), System.currentTimeMillis()).get();
created = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get();
@ -625,7 +675,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
}
@Test
@ -645,7 +695,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm);
Alarm created = result.getAlarm();
Alarm created = new Alarm(result.getAlarmInfo());
PageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(childId)
@ -655,7 +705,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
// Check parent relation
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -666,7 +716,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
Assert.assertTrue("Alarm was not deleted when expected", alarmService.deleteAlarm(tenantId, created.getId()).isSuccessful());

8
rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

@ -415,6 +415,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/clear", null, alarmId.getId());
}
public void assignAlarm(AlarmId alarmId, UserId userId) {
restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/assign/{userId}", null, alarmId.getId(), userId.getId());
}
public void unassignAlarm(AlarmId alarmId) {
restTemplate.delete(baseURL + "/api/alarm/{alarmId}/assign", alarmId.getId());
}
public PageData<AlarmInfo> getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, Boolean fetchOriginator) {
String urlSecondPart = "/api/alarm/{entityType}/{entityId}?fetchOriginator={fetchOriginator}";
Map<String, String> params = new HashMap<>();

9
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java

@ -27,6 +27,7 @@ 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;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
@ -39,7 +40,7 @@ import java.util.Collection;
*/
public interface RuleEngineAlarmService {
Alarm createOrUpdateAlarm(Alarm alarm);
AlarmInfo createOrUpdateAlarm(Alarm alarm);
Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId);
@ -49,6 +50,10 @@ public interface RuleEngineAlarmService {
ListenableFuture<AlarmOperationResult> clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
AlarmInfo assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs);
AlarmInfo unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs);
ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId);
Alarm findAlarmById(TenantId tenantId, AlarmId alarmId);
@ -61,7 +66,7 @@ public interface RuleEngineAlarmService {
ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query);
AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus);
AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId);
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
}

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java

@ -132,7 +132,7 @@ class AlarmState {
);
DonAsynchron.withCallback(alarmClearOperationResult,
result -> {
pushMsg(ctx, msg, new TbAlarmResult(false, false, true, result.getAlarm()), clearState);
pushMsg(ctx, msg, new TbAlarmResult(false, false, true, result.getAlarmInfo()), clearState);
},
throwable -> {
throw new RuntimeException(throwable);

25
ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts

@ -84,10 +84,15 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
{
createdTime: [''],
originatorName: [''],
assigneeFirstName: [''],
assigneeLastName: [''],
assigneeEmail: [''],
assigneeId: [''],
startTime: [''],
endTime: [''],
ackTime: [''],
clearTime: [''],
assignTime: [''],
type: [''],
alarmSeverity: [''],
alarmStatus: [''],
@ -115,6 +120,22 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
.patchValue(this.datePipe.transform(alarm.createdTime, 'yyyy-MM-dd HH:mm:ss'));
this.alarmFormGroup.get('originatorName')
.patchValue(alarm.originatorName);
if(alarm.assigneeFirstName) {
this.alarmFormGroup.get('assigneeFirstName')
.patchValue(alarm.assigneeFirstName);
}
if(alarm.assigneeLastName) {
this.alarmFormGroup.get('assigneeLastName')
.patchValue(alarm.assigneeLastName);
}
if(alarm.assigneeEmail) {
this.alarmFormGroup.get('assigneeEmail')
.patchValue(alarm.assigneeEmail);
}
if(alarm.assigneeId) {
this.alarmFormGroup.get('assigneeId')
.patchValue(alarm.assigneeId.id);
}
if (alarm.startTs) {
this.alarmFormGroup.get('startTime')
.patchValue(this.datePipe.transform(alarm.startTs, 'yyyy-MM-dd HH:mm:ss'));
@ -131,6 +152,10 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
this.alarmFormGroup.get('clearTime')
.patchValue(this.datePipe.transform(alarm.clearTs, 'yyyy-MM-dd HH:mm:ss'));
}
if (alarm.assignTs) {
this.alarmFormGroup.get('assignTime')
.patchValue(this.datePipe.transform(alarm.assignTs, 'yyyy-MM-dd HH:mm:ss'));
}
this.alarmFormGroup.get('type').patchValue(alarm.type);
this.alarmFormGroup.get('alarmSeverity')
.patchValue(this.translate.instant(alarmSeverityTranslations.get(alarm.severity)));

2
ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts

@ -116,7 +116,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
}
fetchAlarms(pageLink: TimePageLink): Observable<PageData<AlarmInfo>> {
const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true);
const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, null, true);
return this.alarmService.getAlarms(query);
}

5
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -89,6 +89,7 @@ import { TbPopoverComponent } from '@shared/components/popover.component';
import { EntityId } from '@shared/models/id/entity-id';
import { AlarmQuery, AlarmSearchStatus, AlarmStatus} from '@app/shared/models/alarm.models';
import { MillisecondsToTimeStringPipe, TelemetrySubscriber } from '@app/shared/public-api';
import { UserId } from '@shared/models/id/user-id';
export interface IWidgetAction {
name: string;
@ -415,8 +416,8 @@ export class WidgetContext {
return new TimePageLink(pageSize, page, textSearch, sortOrder, startTime, endTime);
}
alarmQuery(entityId: EntityId, pageLink: TimePageLink, searchStatus: AlarmSearchStatus, status: AlarmStatus, fetchOriginator: boolean) {
return new AlarmQuery(entityId, pageLink, searchStatus, status, fetchOriginator);
alarmQuery(entityId: EntityId, pageLink: TimePageLink, searchStatus: AlarmSearchStatus, status: AlarmStatus, assigneeId: UserId, fetchOriginator: boolean) {
return new AlarmQuery(entityId, pageLink, searchStatus, status, assigneeId, fetchOriginator);
}
}

50
ui-ngx/src/app/shared/models/alarm.models.ts

@ -23,6 +23,7 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
import { EntityType } from '@shared/models/entity-type.models';
import { CustomerId } from '@shared/models/id/customer-id';
import { TableCellButtonActionDescriptor } from '@home/components/widget/lib/table-widget.models';
import { UserId } from "@shared/models/id/user-id";
export enum AlarmSeverity {
CRITICAL = 'CRITICAL',
@ -89,6 +90,7 @@ export const alarmSeverityColors = new Map<AlarmSeverity, string>(
export interface Alarm extends BaseData<AlarmId> {
tenantId: TenantId;
customerId: CustomerId;
assigneeId: UserId;
type: string;
originator: EntityId;
severity: AlarmSeverity;
@ -97,12 +99,17 @@ export interface Alarm extends BaseData<AlarmId> {
endTs: number;
ackTs: number;
clearTs: number;
assignTs: number;
propagate: boolean;
details?: any;
}
export interface AlarmInfo extends Alarm {
originatorName: string;
originatorLabel: string;
assigneeFirstName: string;
assigneeLastName: string;
assigneeEmail: string;
}
export interface AlarmDataInfo extends AlarmInfo {
@ -115,12 +122,18 @@ export const simulatedAlarm: AlarmInfo = {
id: new AlarmId(NULL_UUID),
tenantId: new TenantId(NULL_UUID),
customerId: new CustomerId(NULL_UUID),
assigneeId: new UserId(NULL_UUID),
createdTime: new Date().getTime(),
startTs: new Date().getTime(),
endTs: 0,
ackTs: 0,
clearTs: 0,
assignTs: 0,
originatorName: 'Simulated',
originatorLabel: 'Simulated',
assigneeFirstName: "",
assigneeLastName: "",
assigneeEmail: "test@example.com",
originator: {
entityType: EntityType.DEVICE,
id: '1'
@ -172,11 +185,22 @@ export const alarmFields: {[fieldName: string]: AlarmField} = {
name: 'alarm.clear-time',
time: true
},
assignTime: {
keyName: 'assignTime',
value: 'assignTs',
name: 'alarm.assign-time',
time: true
},
originator: {
keyName: 'originator',
value: 'originatorName',
name: 'alarm.originator'
},
originatorLabel: {
keyName: 'originatorLabel',
value: 'originatorLabel',
name: 'alarm.originator-label'
},
originatorType: {
keyName: 'originatorType',
value: 'originator.entityType',
@ -196,6 +220,26 @@ export const alarmFields: {[fieldName: string]: AlarmField} = {
keyName: 'status',
value: 'status',
name: 'alarm.status'
},
assigneeId: {
keyName: 'assigneeId',
value: 'assigneeId.id',
name: 'alarm.assignee-id'
},
assigneeFirstName: {
keyName: 'assigneeFirstName',
value: 'assigneeFirstName',
name: 'alarm.assignee-first-name'
},
assigneeLastName: {
keyName: 'assigneeLastName',
value: 'assigneeLastName',
name: 'alarm.assignee-last-name'
},
assigneeEmail: {
keyName: 'assigneeEmail',
value: 'assigneeEmail',
name: 'alarm.assignee-email'
}
};
@ -205,15 +249,17 @@ export class AlarmQuery {
pageLink: TimePageLink;
searchStatus: AlarmSearchStatus;
status: AlarmStatus;
assigneeId: UserId;
fetchOriginator: boolean;
constructor(entityId: EntityId, pageLink: TimePageLink,
searchStatus: AlarmSearchStatus, status: AlarmStatus,
fetchOriginator: boolean) {
assigneeId: UserId, fetchOriginator: boolean) {
this.affectedEntityId = entityId;
this.pageLink = pageLink;
this.searchStatus = searchStatus;
this.status = status;
this.assigneeId = assigneeId;
this.fetchOriginator = fetchOriginator;
}
@ -224,6 +270,8 @@ export class AlarmQuery {
query += `&searchStatus=${this.searchStatus}`;
} else if (this.status) {
query += `&status=${this.status}`;
} else if (this.assigneeId) {
query += `&assigneeId=${this.assigneeId.id}`;
}
if (typeof this.fetchOriginator !== 'undefined' && this.fetchOriginator !== null) {
query += `&fetchOriginator=${this.fetchOriginator}`;

4
ui-ngx/src/app/shared/models/audit-log.models.ts

@ -47,6 +47,8 @@ export enum ActionType {
RELATIONS_DELETED = 'RELATIONS_DELETED',
ALARM_ACK = 'ALARM_ACK',
ALARM_CLEAR = 'ALARM_CLEAR',
ALARM_ASSIGN = 'ALARM_ASSIGN',
ALARM_UNASSIGN = 'ALARM_UNASSIGN',
ADDED_COMMENT = 'ADDED_COMMENT',
UPDATED_COMMENT = 'UPDATED_COMMENT',
DELETED_COMMENT = 'DELETED_COMMENT',
@ -88,6 +90,8 @@ export const actionTypeTranslations = new Map<ActionType, string>(
[ActionType.RELATIONS_DELETED, 'audit-log.type-relations-delete'],
[ActionType.ALARM_ACK, 'audit-log.type-alarm-ack'],
[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'],

8
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -433,7 +433,12 @@
"type": "Type",
"severity": "Severity",
"originator": "Originator",
"originator-label": "Originator label",
"originator-type": "Originator type",
"assignee-id": "Assignee id",
"assignee-first-name": "Assignee first name",
"assignee-last-name": "Assignee last name",
"assignee-email": "Assignee email",
"details": "Details",
"status": "Status",
"alarm-details": "Alarm details",
@ -441,6 +446,7 @@
"end-time": "End time",
"ack-time": "Acknowledged time",
"clear-time": "Cleared time",
"assign-time": "Assign time",
"alarm-severity-list": "Alarm severity list",
"any-severity": "Any severity",
"severity-critical": "Critical",
@ -728,6 +734,8 @@
"type-relations-delete": "All relation deleted",
"type-alarm-ack": "Acknowledged",
"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",

Loading…
Cancel
Save