From 0dcde6044392b8f6f22766a54d7999c80b4f8d96 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 30 Nov 2022 13:06:00 +0200 Subject: [PATCH 01/60] Added alarm assignment feature and tests for it --- .../main/data/upgrade/3.4.2/schema_update.sql | 27 +++++++ .../server/controller/AlarmController.java | 73 +++++++++++++++-- .../controller/ControllerConstants.java | 2 + .../install/ThingsboardInstallService.java | 4 + .../service/action/EntityActionService.java | 6 ++ .../entitiy/AbstractTbEntityService.java | 2 +- .../DefaultTbNotificationEntityService.java | 4 + .../entitiy/alarm/DefaultTbAlarmService.java | 29 +++++++ .../service/entitiy/alarm/TbAlarmService.java | 5 ++ .../install/SqlDatabaseUpgradeService.java | 12 +++ .../update/DefaultDataUpdateService.java | 2 +- .../DefaultAlarmSubscriptionService.java | 19 ++++- .../controller/BaseAlarmControllerTest.java | 79 +++++++++++++++++++ .../server/dao/alarm/AlarmService.java | 7 +- .../server/common/data/DataConstants.java | 2 + .../server/common/data/alarm/Alarm.java | 25 +++--- .../server/common/data/alarm/AlarmQuery.java | 2 + .../server/common/data/alarm/EntityAlarm.java | 2 + .../server/common/data/audit/ActionType.java | 2 + .../common/data/edge/EdgeEventActionType.java | 2 + .../server/dao/alarm/AlarmDao.java | 3 +- .../server/dao/alarm/BaseAlarmService.java | 44 ++++++++++- .../server/dao/audit/AuditLogServiceImpl.java | 2 + .../server/dao/model/ModelConstants.java | 3 + .../dao/model/sql/AbstractAlarmEntity.java | 21 +++++ .../dao/model/sql/EntityAlarmEntity.java | 11 +++ .../server/dao/sql/alarm/AlarmRepository.java | 15 +++- .../server/dao/sql/alarm/JpaAlarmDao.java | 20 ++++- .../resources/sql/schema-entities-idx.sql | 2 + .../main/resources/sql/schema-entities.sql | 3 + .../dao/service/BaseAlarmServiceTest.java | 12 +-- .../engine/api/RuleEngineAlarmService.java | 7 +- .../components/alarm/alarm-table-config.ts | 2 +- .../home/models/widget-component.models.ts | 5 +- ui-ngx/src/app/shared/models/alarm.models.ts | 17 +++- .../src/app/shared/models/audit-log.models.ts | 4 + .../assets/locale/locale.constant-en_US.json | 3 + 37 files changed, 442 insertions(+), 38 deletions(-) create mode 100644 application/src/main/data/upgrade/3.4.2/schema_update.sql diff --git a/application/src/main/data/upgrade/3.4.2/schema_update.sql b/application/src/main/data/upgrade/3.4.2/schema_update.sql new file mode 100644 index 0000000000..e47a6bf2f3 --- /dev/null +++ b/application/src/main/data/upgrade/3.4.2/schema_update.sql @@ -0,0 +1,27 @@ +-- +-- Copyright © 2016-2022 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + + +-- ALARM ASSIGN TO USER START + +ALTER TABLE alarm ADD COLUMN assign_ts BIGINT; +ALTER TABLE alarm ADD COLUMN assignee_id UUID; + +ALTER TABLE entity_alarm ADD COLUMN assignee_id UUID; + +CREATE INDEX IF NOT EXISTS idx_entity_alarm_assignee_id ON entity_alarm(assignee_id); + +-- ALARM ASSIGN TO USER END \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 858a7ee3c4..cb1093c745 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -41,6 +41,7 @@ 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; @@ -48,9 +49,13 @@ import org.thingsboard.server.service.entitiy.alarm.TbAlarmService; 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 +84,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 +185,45 @@ 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 void 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)); + // TODO Add special permissions for assignment + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + UserId assigneeId = new UserId(UUID.fromString(strAssigneeId)); + tbAlarmService.assign(alarm, getCurrentUser(), assigneeId).get(); + } + + @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 void assignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + @PathVariable(ALARM_ID) String strAlarmId + ) throws Exception { + checkParameter(ALARM_ID, strAlarmId); + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + // TODO Add special permissions for unassignment + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + tbAlarmService.unassign(alarm, getCurrentUser()).get(); + } + @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 +239,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 +268,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 +295,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 +320,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 +352,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); @@ -306,9 +365,13 @@ 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)); + } checkEntityId(entityId, Operation.READ); try { - return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus); + return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus, assigneeUserId); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index e00ccaa4bc..27d26164df 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/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 ENTITY_ID_PARAM_DESCRIPTION = "A string value representing the entity id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String OTA_PACKAGE_ID_PARAM_DESCRIPTION = "A string value representing the ota package id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String ENTITY_TYPE_PARAM_DESCRIPTION = "A string value representing the entity type. For example, 'DEVICE'"; diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 9facea6dde..6b6104b0e2 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -236,6 +236,10 @@ public class ThingsboardInstallService { log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); break; + case "3.4.2": + log.info("Upgrading ThingsBoard from version 3.4.2 to 3.5 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.4.2"); + break; //TODO update CacheCleanupService on the next version upgrade diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java index 7057f903be..fc8b71947c 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java +++ b/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_CLEAR; + break; + case ALARM_UNASSIGN: + msgType = DataConstants.ALARM_CLEAR; + break; case ALARM_DELETE: msgType = DataConstants.ALARM_DELETE; break; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java index 9f34c061fe..f4885c56cc 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java @@ -73,7 +73,7 @@ public abstract class AbstractTbEntityService { protected ListenableFuture removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) { ListenableFuture> 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> alarmIdsFuture = Futures.transform(alarmsFuture, page -> page.getData().stream().map(AlarmInfo::getId).collect(Collectors.toList()), dbExecutor); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java index 8f8531a5b4..94370d6ff2 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -306,6 +306,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: diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index 8157eff3e5..023e144bf0 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -29,6 +29,7 @@ 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.service.entitiy.AbstractTbEntityService; import java.util.List; @@ -75,6 +76,34 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb }, MoreExecutors.directExecutor()); } + @Override + public ListenableFuture assign(Alarm alarm, User user, UserId assigneeId) { + long assignTs = System.currentTimeMillis(); + ListenableFuture future = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, assignTs); + return Futures.transform(future, result -> { + if (result != null && result) { + alarm.setAssignTs(assignTs); + alarm.setAssigneeId(assigneeId); + notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ASSIGN, user); + } + return null; + }, MoreExecutors.directExecutor()); + } + + @Override + public ListenableFuture unassign(Alarm alarm, User user) { + long assignTs = System.currentTimeMillis(); + ListenableFuture future = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), assignTs); + return Futures.transform(future, result -> { + if (result != null && result) { + alarm.setAssignTs(assignTs); + alarm.setAssigneeId(null); + notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_UNASSIGN, user); + } + return null; + }, MoreExecutors.directExecutor()); + } + @Override public Boolean delete(Alarm alarm, User user) { TenantId tenantId = alarm.getTenantId(); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java index 8cd8d0b49d..0cf2de5ba9 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java +++ b/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 clear(Alarm alarm, User user); + ListenableFuture assign(Alarm alarm, User user, UserId assigneeId); + + ListenableFuture unassign(Alarm alarm, User user); + Boolean delete(Alarm alarm, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 80665d544f..349400ecd1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -677,6 +677,18 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.error("Failed updating schema!!!", e); } break; + case "3.4.2": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating schema ..."); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.2", SCHEMA_UPDATE_SQL); + loadSql(schemaUpdateFile, conn); + log.info("Updating schema settings..."); + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004003;"); + log.info("Schema updated."); + } catch (Exception e) { + log.error("Failed updating schema!!!", e); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index ee34bd7f23..a831a0af5e 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -555,7 +555,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 alarms = alarmDao.findAlarms(tenantId, alarmQuery); boolean hasNext = true; while (hasNext) { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 956dde8024..f7d121d9ff 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -34,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.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataQuery; @@ -124,6 +125,20 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService return result; } + @Override + public ListenableFuture assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs) { + ListenableFuture result = alarmService.assignAlarm(tenantId, alarmId, assigneeId, assignTs); + Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor); + return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); + } + + @Override + public ListenableFuture unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs) { + ListenableFuture result = alarmService.unassignAlarm(tenantId, alarmId, assignTs); + Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor); + return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); + } + @Override public ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId) { return alarmService.findAlarmByIdAsync(tenantId, alarmId); @@ -150,8 +165,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, UserId assigneeUserId) { + return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus, assigneeUserId); } @Override diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java index 493b1818d9..05b6f15c61 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java @@ -347,6 +347,85 @@ 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 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 testFindAlarmsViaCustomerUser() throws Exception { loginCustomerUser(); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index b8041aa3fc..81b4de263a 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; 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; @@ -48,6 +49,10 @@ public interface AlarmService { ListenableFuture clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs); + ListenableFuture assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs); + + ListenableFuture unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs); + Alarm findAlarmById(TenantId tenantId, AlarmId alarmId); ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId); @@ -59,7 +64,7 @@ public interface AlarmService { ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, - AlarmStatus alarmStatus); + AlarmStatus alarmStatus, UserId assigneeUserId); ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 69c36697d3..099a452712 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -73,6 +73,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"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index f13c84bc82..8eaca6b567 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/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 java.util.List; @@ -58,23 +59,27 @@ public class Alarm extends BaseData 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 propagateRelationTypes; @@ -96,10 +101,12 @@ public class Alarm extends BaseData 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(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java index 25bfefd4b2..945442dc9b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java +++ b/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; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java index e7606d852b..b75adf7ebb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java +++ b/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; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java index 41f6f30dbe..4b666fa425 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -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), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java index bba8767fca..79a689f2e6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java +++ b/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, diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index 87788d1b2b..11c3e47599 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/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 { PageData findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection orderedEntityIds); - Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set status); + Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set status, UserId assigneeUserId); PageData findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index e6012602f1..3abefb780b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -39,6 +39,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; @@ -261,6 +262,42 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ }); } + @Override + public ListenableFuture assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTime) { + return getAndUpdateAsync(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); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + } + } + }); + } + + @Override + public ListenableFuture unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTime) { + return getAndUpdateAsync(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); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + } + } + }); + } + @Override public Alarm findAlarmById(TenantId tenantId, AlarmId alarmId) { log.trace("Executing findAlarmById [{}]", alarmId); @@ -328,7 +365,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, - AlarmStatus alarmStatus) { + AlarmStatus alarmStatus, UserId assigneeUserId) { Set statusList = null; if (alarmSearchStatus != null) { statusList = alarmSearchStatus.getStatuses(); @@ -336,7 +373,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ statusList = Collections.singleton(alarmStatus); } - Set alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList); + Set alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList, assigneeUserId); return alarmSeverities.stream().min(AlarmSeverity::compareTo).orElse(null); } @@ -390,7 +427,8 @@ 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()); + // TODO Add ability to automatically assign created alarm to some user + EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), null,alarm.getId()); try { alarmDao.createEntityAlarmRecord(entityAlarm); } catch (Exception e) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java index 8bb862bea5..8a08abcf3b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java @@ -165,6 +165,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) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index e24bde91fb..c64dea0c40 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -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"; @@ -286,10 +287,12 @@ public class ModelConstants { 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_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"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java index 56adfae6de..c01b145174 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java +++ b/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 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 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 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,6 +158,7 @@ public abstract class AbstractAlarmEntity 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()); @@ -163,6 +178,7 @@ public abstract class AbstractAlarmEntity 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 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 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 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(","))); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java index 1f1a40b25a..434a4662fc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java +++ b/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; @@ -66,6 +68,9 @@ public final class EntityAlarmEntity implements ToData { @Column(name = CUSTOMER_ID_PROPERTY, columnDefinition = "uuid") private UUID customerId; + @Column(name = ASSIGNEE_ID_PROPERTY, columnDefinition = "uuid") + private UUID assigneeId; + public EntityAlarmEntity() { super(); } @@ -80,6 +85,9 @@ public final class EntityAlarmEntity implements ToData { if (entityAlarm.getCustomerId() != null) { customerId = entityAlarm.getCustomerId().getId(); } + if (entityAlarm.getAssigneeId() != null) { + assigneeId = entityAlarm.getAssigneeId().getId(); + } } @Override @@ -93,6 +101,9 @@ public final class EntityAlarmEntity implements ToData { if (customerId != null) { result.setCustomerId(new CustomerId(customerId)); } + if (assigneeId != null) { + result.setAssigneeId(new UserId(assigneeId)); + } return result; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index beadcddf31..24bfe88656 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -48,6 +48,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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 +64,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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,6 +74,7 @@ public interface AlarmRepository extends JpaRepository { @Param("startTime") Long startTime, @Param("endTime") Long endTime, @Param("alarmStatuses") Set alarmStatuses, + @Param("assigneeId") UUID assigneeId, @Param("searchText") String searchText, Pageable pageable); @@ -80,6 +83,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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 +94,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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,6 +102,7 @@ public interface AlarmRepository extends JpaRepository { @Param("startTime") Long startTime, @Param("endTime") Long endTime, @Param("alarmStatuses") Set alarmStatuses, + @Param("assigneeId") UUID assigneeId, @Param("searchText") String searchText, Pageable pageable); @@ -105,6 +111,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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 +123,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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 +132,7 @@ public interface AlarmRepository extends JpaRepository { @Param("startTime") Long startTime, @Param("endTime") Long endTime, @Param("alarmStatuses") Set alarmStatuses, + @Param("assigneeId") UUID assigneeId, @Param("searchText") String searchText, Pageable pageable); @@ -133,11 +142,13 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))") Set findAlarmSeverities(@Param("tenantId") UUID tenantId, @Param("affectedEntityId") UUID affectedEntityId, @Param("affectedEntityType") String affectedEntityType, - @Param("alarmStatuses") Set alarmStatuses); + @Param("alarmStatuses") Set alarmStatuses, + @Param("assigneeId") UUID assigneeId); @Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.createdTime < :time AND a.endTs < :time") Page findAlarmsIdsByEndTsBeforeAndTenantId(@Param("time") Long time, @Param("tenantId") UUID tenantId, Pageable pageable); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 407f7658fe..b862f1c7e7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -32,6 +32,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 +113,10 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } else if (query.getStatus() != null) { statusSet = Collections.singleton(query.getStatus()); } + UUID assigneeId = null; + if (query.getAssigneeId() != null) { + assigneeId = query.getAssigneeId().getId(); + } if (affectedEntity != null) { return DaoUtil.toPageData( alarmRepository.findAlarms( @@ -121,6 +126,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A query.getPageLink().getStartTime(), query.getPageLink().getEndTime(), statusSet, + assigneeId, Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) @@ -132,6 +138,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A query.getPageLink().getStartTime(), query.getPageLink().getEndTime(), statusSet, + assigneeId, Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) @@ -148,6 +155,10 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } else if (query.getStatus() != null) { statusSet = Collections.singleton(query.getStatus()); } + UUID assigneeId = null; + if (query.getAssigneeId() != null) { + assigneeId = query.getAssigneeId().getId(); + } return DaoUtil.toPageData( alarmRepository.findCustomerAlarms( tenantId.getId(), @@ -155,6 +166,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A query.getPageLink().getStartTime(), query.getPageLink().getEndTime(), statusSet, + assigneeId, Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) @@ -167,8 +179,12 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } @Override - public Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set statuses) { - return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses); + public Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set statuses, UserId assigneeUserId) { + UUID assigneeId = null; + if (assigneeUserId != null) { + assigneeId = assigneeUserId.getId(); + } + return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses, assigneeId); } @Override diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index e7586b17a8..2e1a830eb0 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -28,6 +28,8 @@ CREATE INDEX IF NOT EXISTS idx_entity_alarm_created_time ON entity_alarm(tenant_ CREATE INDEX IF NOT EXISTS idx_entity_alarm_alarm_id ON entity_alarm(alarm_id); +CREATE INDEX IF NOT EXISTS idx_entity_alarm_assignee_id ON entity_alarm(assignee_id); + CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id); CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 73039274a6..5ceb03288f 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/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, @@ -69,6 +71,7 @@ CREATE TABLE IF NOT EXISTS entity_alarm ( alarm_type varchar(255) NOT NULL, customer_id uuid, alarm_id uuid, + assignee_id uuid, CONSTRAINT entity_alarm_pkey PRIMARY KEY (entity_id, alarm_id), CONSTRAINT fk_entity_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java index f56936f87e..ba22a57110 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java @@ -425,7 +425,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) @@ -459,11 +459,11 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .build(); alarm3 = alarmService.createOrUpdateAlarm(alarm3).getAlarm(); - 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 diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java index 21bab255f2..e41c7ad5d5 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java +++ b/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; @@ -49,6 +50,10 @@ public interface RuleEngineAlarmService { ListenableFuture clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs); + ListenableFuture assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs); + + ListenableFuture unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs); + ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId); Alarm findAlarmById(TenantId tenantId, AlarmId alarmId); @@ -61,7 +66,7 @@ public interface RuleEngineAlarmService { ListenableFuture> 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, UserId assigneeId); PageData findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection orderedEntityIds); } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index 6cc51857e7..ba70a43c14 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -116,7 +116,7 @@ export class AlarmTableConfig extends EntityTableConfig } fetchAlarms(pageLink: TimePageLink): Observable> { - 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); } diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 813b3b6927..7a7a81e302 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/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 { TelemetrySubscriber } from '@app/shared/public-api'; +import { UserId } from '@shared/models/id/user-id'; export interface IWidgetAction { name: string; @@ -414,8 +415,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); } } diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts index fd2e200090..8059a1f585 100644 --- a/ui-ngx/src/app/shared/models/alarm.models.ts +++ b/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( export interface Alarm extends BaseData { tenantId: TenantId; customerId: CustomerId; + assigneeId: UserId; type: string; originator: EntityId; severity: AlarmSeverity; @@ -97,6 +99,7 @@ export interface Alarm extends BaseData { endTs: number; ackTs: number; clearTs: number; + assignTs: number; propagate: boolean; details?: any; } @@ -115,11 +118,13 @@ 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', originator: { entityType: EntityType.DEVICE, @@ -172,6 +177,12 @@ 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', @@ -205,15 +216,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 +237,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}`; diff --git a/ui-ngx/src/app/shared/models/audit-log.models.ts b/ui-ngx/src/app/shared/models/audit-log.models.ts index 056c6aa569..fd9ca82275 100644 --- a/ui-ngx/src/app/shared/models/audit-log.models.ts +++ b/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', LOGIN = 'LOGIN', LOGOUT = 'LOGOUT', LOCKOUT = 'LOCKOUT', @@ -85,6 +87,8 @@ export const actionTypeTranslations = new Map( [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.LOGIN, 'audit-log.type-login'], [ActionType.LOGOUT, 'audit-log.type-logout'], [ActionType.LOCKOUT, 'audit-log.type-lockout'], diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 3df8a15e52..269e2e0f67 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -440,6 +440,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", @@ -727,6 +728,8 @@ "type-relations-delete": "All relation deleted", "type-alarm-ack": "Acknowledged", "type-alarm-clear": "Cleared", + "type-alarm-assign": "Assigned", + "type-alarm-unassign": "Unassigned", "type-login": "Login", "type-logout": "Logout", "type-lockout": "Lockout", From 6f5a116beda8846c17096b683828d14b36690884 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 30 Nov 2022 14:28:50 +0200 Subject: [PATCH 02/60] Added methods to REST client --- .../main/java/org/thingsboard/rest/client/RestClient.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index ec1ead3e6b..5f3cf943c0 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -414,6 +414,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 getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, Boolean fetchOriginator) { String urlSecondPart = "/api/alarm/{entityType}/{entityId}?fetchOriginator={fetchOriginator}"; Map params = new HashMap<>(); From 229b2973ccf88dc5ff525dfac4adcf7563d43c88 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 5 Dec 2022 15:17:31 +0200 Subject: [PATCH 03/60] Added ability to check permissions for different user than the currect one --- .../server/controller/AlarmController.java | 12 +- .../server/controller/BaseController.java | 183 +++++++++++++----- 2 files changed, 140 insertions(+), 55 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index cb1093c745..fd91abed3a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -46,6 +46,7 @@ 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.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -201,9 +202,15 @@ public class AlarmController extends BaseController { checkParameter(ALARM_ID, strAlarmId); checkParameter(ASSIGNEE_ID, strAssigneeId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - // TODO Add special permissions for assignment - Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); UserId assigneeId = new UserId(UUID.fromString(strAssigneeId)); + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + if (!getCurrentUser().getId().equals(assigneeId)) { + try { + checkEntityId(alarm.getOriginator(), Operation.WRITE, new SecurityUser(assigneeId)); + } catch (Exception e) { + throw new ThingsboardException("Assignee user doesn't have permission for alarm originator", ThingsboardErrorCode.PERMISSION_DENIED); + } + } tbAlarmService.assign(alarm, getCurrentUser(), assigneeId).get(); } @@ -219,7 +226,6 @@ public class AlarmController extends BaseController { ) throws Exception { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - // TODO Add special permissions for unassignment Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); tbAlarmService.unassign(alarm, getCurrentUser()).get(); } diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index ffc3fcf3cd..957b6348a7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -449,11 +449,15 @@ public abstract class BaseController { } Tenant checkTenantId(TenantId tenantId, Operation operation) throws ThingsboardException { + return checkTenantId(tenantId, operation, getCurrentUser()); + } + + Tenant checkTenantId(TenantId tenantId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(tenantId, INCORRECT_TENANT_ID + tenantId); Tenant tenant = tenantService.findTenantById(tenantId); checkNotNull(tenant, "Tenant with id [" + tenantId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant); + accessControlService.checkPermission(securityUser, Resource.TENANT, operation, tenantId, tenant); return tenant; } catch (Exception e) { throw handleException(e, false); @@ -473,11 +477,15 @@ public abstract class BaseController { } TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation) throws ThingsboardException { + return checkTenantProfileId(tenantProfileId, operation, getCurrentUser()); + } + + TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(tenantProfileId, "Incorrect tenantProfileId " + tenantProfileId); TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(getTenantId(), tenantProfileId); checkNotNull(tenantProfile, "Tenant profile with id [" + tenantProfileId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, operation); + accessControlService.checkPermission(securityUser, Resource.TENANT_PROFILE, operation); return tenantProfile; } catch (Exception e) { throw handleException(e, false); @@ -489,11 +497,15 @@ public abstract class BaseController { } Customer checkCustomerId(CustomerId customerId, Operation operation) throws ThingsboardException { + return checkCustomerId(customerId, operation, getCurrentUser()); + } + + Customer checkCustomerId(CustomerId customerId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(customerId, "Incorrect customerId " + customerId); Customer customer = customerService.findCustomerById(getTenantId(), customerId); checkNotNull(customer, "Customer with id [" + customerId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.CUSTOMER, operation, customerId, customer); + accessControlService.checkPermission(securityUser, Resource.CUSTOMER, operation, customerId, customer); return customer; } catch (Exception e) { throw handleException(e, false); @@ -501,11 +513,15 @@ public abstract class BaseController { } User checkUserId(UserId userId, Operation operation) throws ThingsboardException { + return checkUserId(userId, operation, getCurrentUser()); + } + + User checkUserId(UserId userId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(userId, "Incorrect userId " + userId); - User user = userService.findUserById(getCurrentUser().getTenantId(), userId); + User user = userService.findUserById(securityUser.getTenantId(), userId); checkNotNull(user, "User with id [" + userId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.USER, operation, userId, user); + accessControlService.checkPermission(securityUser, Resource.USER, operation, userId, user); return user; } catch (Exception e) { throw handleException(e, false); @@ -522,6 +538,10 @@ public abstract class BaseController { } protected void checkEntityId(EntityId entityId, Operation operation) throws ThingsboardException { + checkEntityId(entityId, operation, getCurrentUser()); + } + + protected void checkEntityId(EntityId entityId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { if (entityId == null) { throw new ThingsboardException("Parameter entityId can't be empty!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); @@ -529,61 +549,61 @@ public abstract class BaseController { validateId(entityId.getId(), "Incorrect entityId " + entityId); switch (entityId.getEntityType()) { case ALARM: - checkAlarmId(new AlarmId(entityId.getId()), operation); + checkAlarmId(new AlarmId(entityId.getId()), operation, securityUser); return; case DEVICE: - checkDeviceId(new DeviceId(entityId.getId()), operation); + checkDeviceId(new DeviceId(entityId.getId()), operation, securityUser); return; case DEVICE_PROFILE: - checkDeviceProfileId(new DeviceProfileId(entityId.getId()), operation); + checkDeviceProfileId(new DeviceProfileId(entityId.getId()), operation, securityUser); return; case CUSTOMER: - checkCustomerId(new CustomerId(entityId.getId()), operation); + checkCustomerId(new CustomerId(entityId.getId()), operation, securityUser); return; case TENANT: - checkTenantId(TenantId.fromUUID(entityId.getId()), operation); + checkTenantId(TenantId.fromUUID(entityId.getId()), operation, securityUser); return; case TENANT_PROFILE: - checkTenantProfileId(new TenantProfileId(entityId.getId()), operation); + checkTenantProfileId(new TenantProfileId(entityId.getId()), operation, securityUser); return; case RULE_CHAIN: - checkRuleChain(new RuleChainId(entityId.getId()), operation); + checkRuleChain(new RuleChainId(entityId.getId()), operation, securityUser); return; case RULE_NODE: - checkRuleNode(new RuleNodeId(entityId.getId()), operation); + checkRuleNode(new RuleNodeId(entityId.getId()), operation, securityUser); return; case ASSET: - checkAssetId(new AssetId(entityId.getId()), operation); + checkAssetId(new AssetId(entityId.getId()), operation, securityUser); return; case ASSET_PROFILE: - checkAssetProfileId(new AssetProfileId(entityId.getId()), operation); + checkAssetProfileId(new AssetProfileId(entityId.getId()), operation, securityUser); return; case DASHBOARD: - checkDashboardId(new DashboardId(entityId.getId()), operation); + checkDashboardId(new DashboardId(entityId.getId()), operation, securityUser); return; case USER: - checkUserId(new UserId(entityId.getId()), operation); + checkUserId(new UserId(entityId.getId()), operation, securityUser); return; case ENTITY_VIEW: - checkEntityViewId(new EntityViewId(entityId.getId()), operation); + checkEntityViewId(new EntityViewId(entityId.getId()), operation, securityUser); return; case EDGE: - checkEdgeId(new EdgeId(entityId.getId()), operation); + checkEdgeId(new EdgeId(entityId.getId()), operation, securityUser); return; case WIDGETS_BUNDLE: - checkWidgetsBundleId(new WidgetsBundleId(entityId.getId()), operation); + checkWidgetsBundleId(new WidgetsBundleId(entityId.getId()), operation, securityUser); return; case WIDGET_TYPE: - checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation); + checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation, securityUser); return; case TB_RESOURCE: - checkResourceId(new TbResourceId(entityId.getId()), operation); + checkResourceId(new TbResourceId(entityId.getId()), operation, securityUser); return; case OTA_PACKAGE: - checkOtaPackageId(new OtaPackageId(entityId.getId()), operation); + checkOtaPackageId(new OtaPackageId(entityId.getId()), operation, securityUser); return; case QUEUE: - checkQueueId(new QueueId(entityId.getId()), operation); + checkQueueId(new QueueId(entityId.getId()), operation, securityUser); return; default: throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); @@ -592,13 +612,16 @@ public abstract class BaseController { throw handleException(e, false); } } - Device checkDeviceId(DeviceId deviceId, Operation operation) throws ThingsboardException { + return checkDeviceId(deviceId, operation, getCurrentUser()); + } + + Device checkDeviceId(DeviceId deviceId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(deviceId, "Incorrect deviceId " + deviceId); - Device device = deviceService.findDeviceById(getCurrentUser().getTenantId(), deviceId); + Device device = deviceService.findDeviceById(securityUser.getTenantId(), deviceId); checkNotNull(device, "Device with id [" + deviceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device); + accessControlService.checkPermission(securityUser, Resource.DEVICE, operation, deviceId, device); return device; } catch (Exception e) { throw handleException(e, false); @@ -618,11 +641,15 @@ public abstract class BaseController { } DeviceProfile checkDeviceProfileId(DeviceProfileId deviceProfileId, Operation operation) throws ThingsboardException { + return checkDeviceProfileId(deviceProfileId, operation, getCurrentUser()); + } + + DeviceProfile checkDeviceProfileId(DeviceProfileId deviceProfileId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(deviceProfileId, "Incorrect deviceProfileId " + deviceProfileId); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(getCurrentUser().getTenantId(), deviceProfileId); + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(securityUser.getTenantId(), deviceProfileId); checkNotNull(deviceProfile, "Device profile with id [" + deviceProfileId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile); + accessControlService.checkPermission(securityUser, Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile); return deviceProfile; } catch (Exception e) { throw handleException(e, false); @@ -630,11 +657,15 @@ public abstract class BaseController { } protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { + return checkEntityViewId(entityViewId, operation, getCurrentUser()); + } + + protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(entityViewId, "Incorrect entityViewId " + entityViewId); - EntityView entityView = entityViewService.findEntityViewById(getCurrentUser().getTenantId(), entityViewId); + EntityView entityView = entityViewService.findEntityViewById(securityUser.getTenantId(), entityViewId); checkNotNull(entityView, "Entity view with id [" + entityViewId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); + accessControlService.checkPermission(securityUser, Resource.ENTITY_VIEW, operation, entityViewId, entityView); return entityView; } catch (Exception e) { throw handleException(e, false); @@ -654,11 +685,15 @@ public abstract class BaseController { } Asset checkAssetId(AssetId assetId, Operation operation) throws ThingsboardException { + return checkAssetId(assetId, operation, getCurrentUser()); + } + + Asset checkAssetId(AssetId assetId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(assetId, "Incorrect assetId " + assetId); - Asset asset = assetService.findAssetById(getCurrentUser().getTenantId(), assetId); + Asset asset = assetService.findAssetById(securityUser.getTenantId(), assetId); checkNotNull(asset, "Asset with id [" + assetId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); + accessControlService.checkPermission(securityUser, Resource.ASSET, operation, assetId, asset); return asset; } catch (Exception e) { throw handleException(e, false); @@ -678,11 +713,15 @@ public abstract class BaseController { } AssetProfile checkAssetProfileId(AssetProfileId assetProfileId, Operation operation) throws ThingsboardException { + return checkAssetProfileId(assetProfileId, operation, getCurrentUser()); + } + + AssetProfile checkAssetProfileId(AssetProfileId assetProfileId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(assetProfileId, "Incorrect assetProfileId " + assetProfileId); - AssetProfile assetProfile = assetProfileService.findAssetProfileById(getCurrentUser().getTenantId(), assetProfileId); + AssetProfile assetProfile = assetProfileService.findAssetProfileById(securityUser.getTenantId(), assetProfileId); checkNotNull(assetProfile, "Asset profile with id [" + assetProfileId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ASSET_PROFILE, operation, assetProfileId, assetProfile); + accessControlService.checkPermission(securityUser, Resource.ASSET_PROFILE, operation, assetProfileId, assetProfile); return assetProfile; } catch (Exception e) { throw handleException(e, false); @@ -690,11 +729,15 @@ public abstract class BaseController { } Alarm checkAlarmId(AlarmId alarmId, Operation operation) throws ThingsboardException { + return checkAlarmId(alarmId, operation, getCurrentUser()); + } + + Alarm checkAlarmId(AlarmId alarmId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(alarmId, "Incorrect alarmId " + alarmId); - Alarm alarm = alarmService.findAlarmByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); + Alarm alarm = alarmService.findAlarmByIdAsync(securityUser.getTenantId(), alarmId).get(); checkNotNull(alarm, "Alarm with id [" + alarmId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarm); + accessControlService.checkPermission(securityUser, Resource.ALARM, operation, alarmId, alarm); return alarm; } catch (Exception e) { throw handleException(e, false); @@ -714,11 +757,15 @@ public abstract class BaseController { } WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, Operation operation) throws ThingsboardException { + return checkWidgetsBundleId(widgetsBundleId, operation, getCurrentUser()); + } + + WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId); - WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(getCurrentUser().getTenantId(), widgetsBundleId); + WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(securityUser.getTenantId(), widgetsBundleId); checkNotNull(widgetsBundle, "Widgets bundle with id [" + widgetsBundleId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.WIDGETS_BUNDLE, operation, widgetsBundleId, widgetsBundle); + accessControlService.checkPermission(securityUser, Resource.WIDGETS_BUNDLE, operation, widgetsBundleId, widgetsBundle); return widgetsBundle; } catch (Exception e) { throw handleException(e, false); @@ -726,11 +773,15 @@ public abstract class BaseController { } WidgetTypeDetails checkWidgetTypeId(WidgetTypeId widgetTypeId, Operation operation) throws ThingsboardException { + return checkWidgetTypeId(widgetTypeId, operation, getCurrentUser()); + } + + WidgetTypeDetails checkWidgetTypeId(WidgetTypeId widgetTypeId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId); - WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(getCurrentUser().getTenantId(), widgetTypeId); + WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(securityUser.getTenantId(), widgetTypeId); checkNotNull(widgetTypeDetails, "Widget type with id [" + widgetTypeId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation, widgetTypeId, widgetTypeDetails); + accessControlService.checkPermission(securityUser, Resource.WIDGET_TYPE, operation, widgetTypeId, widgetTypeDetails); return widgetTypeDetails; } catch (Exception e) { throw handleException(e, false); @@ -738,11 +789,15 @@ public abstract class BaseController { } Dashboard checkDashboardId(DashboardId dashboardId, Operation operation) throws ThingsboardException { + return checkDashboardId(dashboardId, operation, getCurrentUser()); + } + + Dashboard checkDashboardId(DashboardId dashboardId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(dashboardId, "Incorrect dashboardId " + dashboardId); - Dashboard dashboard = dashboardService.findDashboardById(getCurrentUser().getTenantId(), dashboardId); + Dashboard dashboard = dashboardService.findDashboardById(securityUser.getTenantId(), dashboardId); checkNotNull(dashboard, "Dashboard with id [" + dashboardId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboard); + accessControlService.checkPermission(securityUser, Resource.DASHBOARD, operation, dashboardId, dashboard); return dashboard; } catch (Exception e) { throw handleException(e, false); @@ -750,11 +805,15 @@ public abstract class BaseController { } Edge checkEdgeId(EdgeId edgeId, Operation operation) throws ThingsboardException { + return checkEdgeId(edgeId, operation, getCurrentUser()); + } + + Edge checkEdgeId(EdgeId edgeId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(edgeId, "Incorrect edgeId " + edgeId); Edge edge = edgeService.findEdgeById(getTenantId(), edgeId); checkNotNull(edge, "Edge with id [" + edgeId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge); + accessControlService.checkPermission(securityUser, Resource.EDGE, operation, edgeId, edge); return edge; } catch (Exception e) { throw handleException(e, false); @@ -813,14 +872,22 @@ public abstract class BaseController { } protected RuleChain checkRuleChain(RuleChainId ruleChainId, Operation operation) throws ThingsboardException { + return checkRuleChain(ruleChainId, operation, getCurrentUser()); + } + + protected RuleChain checkRuleChain(RuleChainId ruleChainId, Operation operation, SecurityUser securityUser) throws ThingsboardException { validateId(ruleChainId, "Incorrect ruleChainId " + ruleChainId); - RuleChain ruleChain = ruleChainService.findRuleChainById(getCurrentUser().getTenantId(), ruleChainId); + RuleChain ruleChain = ruleChainService.findRuleChainById(securityUser.getTenantId(), ruleChainId); checkNotNull(ruleChain, "Rule chain with id [" + ruleChainId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.RULE_CHAIN, operation, ruleChainId, ruleChain); + accessControlService.checkPermission(securityUser, Resource.RULE_CHAIN, operation, ruleChainId, ruleChain); return ruleChain; } protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation) throws ThingsboardException { + return checkRuleNode(ruleNodeId, operation, getCurrentUser()); + } + + protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation, SecurityUser securityUser) throws ThingsboardException { validateId(ruleNodeId, "Incorrect ruleNodeId " + ruleNodeId); RuleNode ruleNode = ruleChainService.findRuleNodeById(getTenantId(), ruleNodeId); checkNotNull(ruleNode, "Rule node with id [" + ruleNodeId + "] is not found"); @@ -829,11 +896,15 @@ public abstract class BaseController { } TbResource checkResourceId(TbResourceId resourceId, Operation operation) throws ThingsboardException { + return checkResourceId(resourceId, operation, getCurrentUser()); + } + + TbResource checkResourceId(TbResourceId resourceId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(resourceId, "Incorrect resourceId " + resourceId); - TbResource resource = resourceService.findResourceById(getCurrentUser().getTenantId(), resourceId); + TbResource resource = resourceService.findResourceById(securityUser.getTenantId(), resourceId); checkNotNull(resource, "Resource with id [" + resourceId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resource); + accessControlService.checkPermission(securityUser, Resource.TB_RESOURCE, operation, resourceId, resource); return resource; } catch (Exception e) { throw handleException(e, false); @@ -853,11 +924,15 @@ public abstract class BaseController { } OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException { + return checkOtaPackageId(otaPackageId, operation, getCurrentUser()); + } + + OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation, SecurityUser securityUser) throws ThingsboardException { try { validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId); - OtaPackage otaPackage = otaPackageService.findOtaPackageById(getCurrentUser().getTenantId(), otaPackageId); + OtaPackage otaPackage = otaPackageService.findOtaPackageById(securityUser.getTenantId(), otaPackageId); checkNotNull(otaPackage, "OTA package with id [" + otaPackageId + "] is not found"); - accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage); + accessControlService.checkPermission(securityUser, Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage); return otaPackage; } catch (Exception e) { throw handleException(e, false); @@ -889,10 +964,14 @@ public abstract class BaseController { } protected Queue checkQueueId(QueueId queueId, Operation operation) throws ThingsboardException { + return checkQueueId(queueId, operation, getCurrentUser()); + } + + protected Queue checkQueueId(QueueId queueId, Operation operation, SecurityUser securityUser) throws ThingsboardException { validateId(queueId, "Incorrect queueId " + queueId); - Queue queue = queueService.findQueueById(getCurrentUser().getTenantId(), queueId); + Queue queue = queueService.findQueueById(securityUser.getTenantId(), queueId); checkNotNull(queue); - accessControlService.checkPermission(getCurrentUser(), Resource.QUEUE, operation, queueId, queue); + accessControlService.checkPermission(securityUser, Resource.QUEUE, operation, queueId, queue); TenantId tenantId = getTenantId(); if (queue.getTenantId().isNullUid() && !tenantId.isNullUid()) { TenantProfile tenantProfile = tenantProfileCache.get(tenantId); From ac83e379ab26a3d715afc3a52396724ba6b2ca01 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 6 Dec 2022 15:33:21 +0200 Subject: [PATCH 04/60] Updated upgrade script --- application/src/main/data/upgrade/3.4.2/schema_update.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/data/upgrade/3.4.2/schema_update.sql b/application/src/main/data/upgrade/3.4.2/schema_update.sql index e47a6bf2f3..de7cd5b8b1 100644 --- a/application/src/main/data/upgrade/3.4.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.2/schema_update.sql @@ -17,10 +17,10 @@ -- ALARM ASSIGN TO USER START -ALTER TABLE alarm ADD COLUMN assign_ts BIGINT; -ALTER TABLE alarm ADD COLUMN assignee_id UUID; +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assign_ts BIGINT; +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assignee_id UUID; -ALTER TABLE entity_alarm ADD COLUMN assignee_id UUID; +ALTER TABLE entity_alarm ADD COLUMN IF NOT EXISTS assignee_id UUID; CREATE INDEX IF NOT EXISTS idx_entity_alarm_assignee_id ON entity_alarm(assignee_id); From 08e356a4152aef41c895c3fb0206ab3707a48d0c Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 12 Dec 2022 13:00:22 +0200 Subject: [PATCH 05/60] Updated alarm assignment to send websocket update on assign, added originator label to alarm info --- .../queue/DefaultTbCoreConsumerService.java | 4 +- .../DefaultSubscriptionManagerService.java | 15 ++- .../SubscriptionManagerService.java | 3 +- .../subscription/TbAlarmDataSubCtx.java | 3 +- .../subscription/TbSubscriptionUtils.java | 4 +- .../DefaultAlarmSubscriptionService.java | 8 +- .../sub/AlarmSubscriptionUpdate.java | 12 +- common/cluster-api/src/main/proto/queue.proto | 2 + .../server/dao/alarm/AlarmAdditionalInfo.java | 32 +++++ .../dao/alarm/AlarmOperationResult.java | 14 +- .../server/common/data/alarm/AlarmInfo.java | 50 +++++-- .../server/common/data/query/AlarmData.java | 10 +- .../server/dao/alarm/BaseAlarmService.java | 123 +++++++++++++++++- .../server/dao/model/ModelConstants.java | 4 + .../server/dao/model/sql/AlarmInfoEntity.java | 14 +- .../dao/sql/query/AlarmDataAdapter.java | 24 +++- .../query/DefaultAlarmQueryRepository.java | 16 ++- .../alarm/alarm-details-dialog.component.ts | 25 ++++ ui-ngx/src/app/shared/models/alarm.models.ts | 26 ++++ .../assets/locale/locale.constant-en_US.json | 4 + 20 files changed, 351 insertions(+), 42 deletions(-) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 931acf908c..c2acd77953 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/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,7 +503,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService { if (TbSubscriptionType.ALARMS.equals(s.getType())) { @@ -303,6 +304,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene }, s -> alarm.getCreatedTime() >= s.getTs(), s -> alarm, + alarmInfo, false ); callback.onSuccess(); @@ -320,6 +322,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene }, s -> alarm.getCreatedTime() >= s.getTs(), s -> alarm, + null, true ); callback.onSuccess(); @@ -414,18 +417,19 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene private void onLocalAlarmSubUpdate(EntityId entityId, Function castFunction, Predicate filterFunction, - Function processFunction, boolean deleted) { + Function processFunction, AlarmInfo alarmInfo, + boolean deleted) { Set 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) { if (serviceId.equals(s.getServiceId())) { - AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, deleted); + AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, 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, alarm, alarmInfo, deleted), null); } } }); @@ -562,12 +566,13 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg); } - private TbProtoQueueMsg toProto(TbSubscription subscription, Alarm alarm, boolean deleted) { + private TbProtoQueueMsg toProto(TbSubscription subscription, Alarm alarm, AlarmInfo alarmInfo, boolean deleted) { TbAlarmSubscriptionUpdateProto.Builder builder = TbAlarmSubscriptionUpdateProto.newBuilder(); builder.setSessionId(subscription.getSessionId()); builder.setSubscriptionId(subscription.getSubscriptionId()); builder.setAlarm(JacksonUtil.toString(alarm)); + builder.setAlarmInfo(JacksonUtil.toString(alarmInfo)); builder.setDeleted(deleted); ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg( diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java index 15aab86327..747a73ebe1 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java @@ -17,6 +17,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,7 +43,7 @@ public interface SubscriptionManagerService extends ApplicationListener keys, TbCallback callback); - void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); + void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, AlarmInfo alarmInfo, TbCallback callback); void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java index 5804002c77..7afc82c03b 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java +++ b/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; @@ -219,7 +220,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { boolean matchesFilter = filter(alarm); if (onCurrentPage) { if (matchesFilter) { - AlarmData updated = new AlarmData(alarm, current.getOriginatorName(), current.getEntityId()); + AlarmData updated = new AlarmData(alarm, subscriptionUpdate.getAlarmInfo(), current.getEntityId()); updated.getLatest().putAll(current.getLatest()); alarmsMap.put(alarmId, updated); sendWsMsg(new AlarmDataUpdate(cmdId, null, Collections.singletonList(updated), maxEntitiesPerAlarmSubscription, data.getTotalElements())); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java index f8359a21ee..91583e1194 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java +++ b/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; @@ -190,7 +191,8 @@ public class TbSubscriptionUtils { 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.getAlarmInfo(), AlarmInfo.class); + return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm, alarmInfo); } } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index f7d121d9ff..634c90880a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -187,7 +187,13 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); if (currentPartitions.contains(tpi)) { if (subscriptionManagerService.isPresent()) { - subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY); + AlarmInfo alarmInfo = new AlarmInfo(alarm); + alarmInfo.setOriginatorName(result.getAlarmAdditionalInfo().getOriginatorName()); + alarmInfo.setOriginatorLabel(result.getAlarmAdditionalInfo().getOriginatorName()); + alarmInfo.setAssigneeFirstName(result.getAlarmAdditionalInfo().getFirstName()); + alarmInfo.setAssigneeLastName(result.getAlarmAdditionalInfo().getLastName()); + alarmInfo.setAssigneeEmail(result.getAlarmAdditionalInfo().getEmail()); + subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, alarmInfo, TbCallback.EMPTY); } else { log.warn("Possible misconfiguration because subscriptionManagerService is null!"); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java index 3f4bc9ce1e..10b750c5c7 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java +++ b/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; @@ -38,16 +39,19 @@ public class AlarmSubscriptionUpdate { @Getter private Alarm alarm; @Getter + private AlarmInfo alarmInfo; + @Getter private boolean alarmDeleted; - public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm) { - this(subscriptionId, alarm, false); + public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, AlarmInfo alarmInfo) { + this(subscriptionId, alarm, alarmInfo, false); } - public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, boolean alarmDeleted) { + public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, AlarmInfo alarmInfo, boolean alarmDeleted) { super(); this.subscriptionId = subscriptionId; this.alarm = alarm; + this.alarmInfo = alarmInfo; this.alarmDeleted = alarmDeleted; } @@ -65,6 +69,6 @@ public class AlarmSubscriptionUpdate { @Override public String toString() { return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", alarm=" - + alarm + "]"; + + alarm + ", alarmInfo=" + alarmInfo + "]"; } } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 5a28d42546..4ef017715d 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -562,6 +562,7 @@ message TbAlarmSubscriptionUpdateProto { string errorMsg = 4; string alarm = 5; bool deleted = 6; + string alarmInfo = 7; } message TbAttributeUpdateProto { @@ -581,6 +582,7 @@ message TbAlarmUpdateProto { int64 tenantIdMSB = 4; int64 tenantIdLSB = 5; string alarm = 6; + string alarmInfo = 7; } message TbAlarmDeleteProto { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java new file mode 100644 index 0000000000..a726ca599a --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.alarm; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +@AllArgsConstructor +public class AlarmAdditionalInfo { + private final String originatorName; + private final String originatorLabel; + + private final String firstName; + private final String lastName; + private final String email; +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java index 4940c74191..c8d2c0aad9 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java @@ -28,19 +28,25 @@ public class AlarmOperationResult { private final boolean successful; private final boolean created; private final List propagatedEntitiesList; + private final AlarmAdditionalInfo alarmAdditionalInfo; public AlarmOperationResult(Alarm alarm, boolean successful) { - this(alarm, successful, Collections.emptyList()); + this(alarm, successful, Collections.emptyList(), new AlarmAdditionalInfo(null, null, null, null, null)); } - public AlarmOperationResult(Alarm alarm, boolean successful, List propagatedEntitiesList) { - this(alarm, successful, false, propagatedEntitiesList); + public AlarmOperationResult(Alarm alarm, boolean successful, AlarmAdditionalInfo alarmAdditionalInfo) { + this(alarm, successful, Collections.emptyList(), alarmAdditionalInfo); } - public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List propagatedEntitiesList) { + public AlarmOperationResult(Alarm alarm, boolean successful, List propagatedEntitiesList, AlarmAdditionalInfo alarmAdditionalInfo) { + this(alarm, successful, false, propagatedEntitiesList, alarmAdditionalInfo); + } + + public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List propagatedEntitiesList, AlarmAdditionalInfo alarmAdditionalInfo) { this.alarm = alarm; this.successful = successful; this.created = created; this.propagatedEntitiesList = propagatedEntitiesList; + this.alarmAdditionalInfo = alarmAdditionalInfo; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index 54537276bd..9b55324800 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/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,13 @@ public class AlarmInfo extends Alarm { super(alarm); } - public AlarmInfo(Alarm alarm, String originatorName) { + public AlarmInfo(Alarm alarm, AlarmInfo alarmInfo) { super(alarm); - this.originatorName = originatorName; - } - - public String getOriginatorName() { - return originatorName; - } - - public void setOriginatorName(String originatorName) { - this.originatorName = originatorName; + originatorName = alarmInfo.originatorName; + originatorLabel = alarmInfo.originatorLabel; + assigneeFirstName = alarmInfo.assigneeFirstName; + assigneeLastName = alarmInfo.assigneeLastName; + assigneeEmail = alarmInfo.assigneeEmail; } @Override @@ -55,8 +78,11 @@ 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 diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java index c288fec13e..cae512af18 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java +++ b/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> latest; - public AlarmData(Alarm alarm, String originatorName, EntityId entityId) { - super(alarm, originatorName); + public AlarmData(Alarm alarm, AlarmInfo alarmInfo, EntityId entityId) { + super(alarm, alarmInfo); + this.entityId = entityId; + this.latest = new HashMap<>(); + } + + public AlarmData(Alarm alarm, EntityId entityId) { + super(alarm); this.entityId = entityId; this.latest = new HashMap<>(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 3abefb780b..1ccac7a19b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -27,6 +27,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityView; +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; @@ -34,10 +40,15 @@ import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.EntityAlarm; +import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException; import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; @@ -47,9 +58,16 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; +import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.EntityService; +import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.user.UserService; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -81,6 +99,27 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Autowired private EntityService entityService; + @Autowired + private UserService userService; + + @Autowired + private TenantService tenantService; + + @Autowired + private CustomerService customerService; + + @Autowired + private DashboardService dashboardService; + + @Autowired + private AssetService assetService; + + @Autowired + private DeviceService deviceService; + + @Autowired + private EntityViewService entityViewService; + @Autowired private DataValidator alarmDataValidator; @@ -151,7 +190,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))); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); + AlarmOperationResult result = new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); deleteEntityRelations(tenantId, alarm.getId()); alarmDao.removeById(tenantId, alarm.getUuidId()); return result; @@ -161,7 +201,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ log.debug("New Alarm : {}", alarm); Alarm saved = alarmDao.save(alarm.getTenantId(), alarm); List propagatedEntitiesList = createEntityAlarmRecords(saved); - return new AlarmOperationResult(saved, true, true, propagatedEntitiesList); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(alarm.getTenantId(), alarm); + return new AlarmOperationResult(saved, true, true, propagatedEntitiesList, alarmAdditionalInfo); } private List createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException { @@ -216,7 +257,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } else { propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result)); } - return new AlarmOperationResult(result, true, propagatedEntitiesList); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(newAlarm.getTenantId(), newAlarm); + return new AlarmOperationResult(result, true, propagatedEntitiesList, alarmAdditionalInfo); } @Override @@ -233,7 +275,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))); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); } } }); @@ -256,7 +299,8 @@ 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))); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); } } }); @@ -274,7 +318,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssigneeId(assigneeId); alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); } } }); @@ -292,7 +337,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssigneeId(null); alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); + return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); } } }); @@ -396,10 +442,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()); @@ -447,4 +497,63 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ Alarm entity = alarmDao.findAlarmById(tenantId, alarmId.getId()); return function.apply(entity); } + + private AlarmAdditionalInfo getAlarmAdditionalInfo(TenantId tenantId, Alarm alarm) { + AlarmAdditionalInfo.AlarmAdditionalInfoBuilder builder = AlarmAdditionalInfo.builder(); + + addAlarmOriginatorNameAndLabel(tenantId, alarm.getOriginator(), builder); + + if (alarm.getAssigneeId() != null) { + User assignedUser = userService.findUserById(tenantId, alarm.getAssigneeId()); + builder.firstName(assignedUser.getFirstName()); + builder.lastName(assignedUser.getLastName()); + builder.email(assignedUser.getEmail()); + } + return builder.build(); + } + + private void addAlarmOriginatorNameAndLabel(TenantId tenantId, EntityId originatorId, AlarmAdditionalInfo.AlarmAdditionalInfoBuilder builder) { + String originatorName = "Unknown"; + String originatorLabel = "Unknown"; + switch(originatorId.getEntityType()) { + case TENANT: + Tenant tenantOriginator = tenantService.findTenantById((TenantId) originatorId); + originatorName = tenantOriginator.getTitle(); + originatorLabel = tenantOriginator.getEmail(); + break; + case CUSTOMER: + Customer customerOriginator = customerService.findCustomerById(tenantId, (CustomerId) originatorId); + originatorName = customerOriginator.getTitle(); + originatorLabel = customerOriginator.getEmail(); + break; + case USER: + User userOriginator = userService.findUserById(tenantId, (UserId) originatorId); + originatorName = userOriginator.getEmail(); + originatorLabel = userOriginator.getName(); + break; + case DASHBOARD: + Dashboard dashboardOriginator = dashboardService.findDashboardById(tenantId, (DashboardId) originatorId); + originatorName = dashboardOriginator.getTitle(); + originatorLabel = dashboardOriginator.getName(); + break; + case ASSET: + Asset assetOriginator = assetService.findAssetById(tenantId, (AssetId) originatorId); + originatorName = assetOriginator.getName(); + originatorLabel = assetOriginator.getLabel(); + break; + case DEVICE: + Device deviceOriginator = deviceService.findDeviceById(tenantId, (DeviceId) originatorId); + originatorName = deviceOriginator.getName(); + originatorLabel = deviceOriginator.getLabel(); + break; + case ENTITY_VIEW: + EntityView entityViewOriginator = entityViewService.findEntityViewById(tenantId, (EntityViewId) originatorId); + originatorName = entityViewOriginator.getName(); + originatorLabel = entityViewOriginator.getType(); // TODO Should we use something else? + break; + } + + builder.originatorName(originatorName); + builder.originatorLabel(originatorLabel); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index c64dea0c40..da2ffc7c89 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -284,10 +284,14 @@ 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"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java index cef7c47a73..aada0c7c17 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java @@ -24,6 +24,11 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; public class AlarmInfoEntity extends AbstractAlarmEntity { private String originatorName; + private String originatorLabel; + + private String assigneeFirstName; + private String assigneeLastName; + private String assigneeEmail; public AlarmInfoEntity() { super(); @@ -35,6 +40,13 @@ public class AlarmInfoEntity extends AbstractAlarmEntity { @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; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java index ff156eb51c..e7ea6a1b66 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java +++ b/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 = row.get(ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY).toString(); + assigneeLastName = row.get(ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY).toString(); + assigneeEmail = row.get(ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY).toString(); + } 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; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index d91a8c5fc4..d3b8137f8b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/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("assigneeId", ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY); alarmFieldColumnMap.put("originator", "originator_name"); + 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" + @@ -87,10 +93,15 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { " THEN (select name from entity_view where id = a.originator_id)" + " END, 'Deleted') as originator_name"; + 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.additional_info as additional_info," + " a.end_ts as end_ts," + " a.originator_id as originator_id," + @@ -104,9 +115,11 @@ 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_ASSIGNEE_INFO + ", "; 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 +152,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)) { diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts index be3aff8c50..f8c257e641 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts @@ -84,10 +84,15 @@ export class AlarmDetailsDialogComponent extends DialogComponent { export interface AlarmInfo extends Alarm { originatorName: string; + assigneeFirstName: string; + assigneeLastName: string; + assigneeEmail: string; } export interface AlarmDataInfo extends AlarmInfo { @@ -126,6 +129,9 @@ export const simulatedAlarm: AlarmInfo = { clearTs: 0, assignTs: 0, originatorName: 'Simulated', + assigneeFirstName: "", + assigneeLastName: "", + assigneeEmail: "test@example.com", originator: { entityType: EntityType.DEVICE, id: '1' @@ -207,6 +213,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' } }; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 269e2e0f67..d191442427 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -433,6 +433,10 @@ "severity": "Severity", "originator": "Originator", "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", From f29d1b0effe02a8ce9639b03f845f9bea63f7b88 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 13 Dec 2022 13:39:09 +0200 Subject: [PATCH 06/60] Added HasLabel, HasEmail, HasTitle interfaces to get correctly label in entityService, refactoring for alarms processing. The main point is swithing from Alarm entity to AlarmInfo --- .../queue/DefaultTbCoreConsumerService.java | 3 +- .../DefaultSubscriptionManagerService.java | 27 ++-- .../SubscriptionManagerService.java | 5 +- .../subscription/TbAlarmDataSubCtx.java | 10 +- .../subscription/TbSubscriptionUtils.java | 11 +- .../DefaultAlarmSubscriptionService.java | 24 ++- .../sub/AlarmSubscriptionUpdate.java | 13 +- .../service/ttl/AlarmsCleanUpService.java | 5 +- common/cluster-api/src/main/proto/queue.proto | 8 +- .../dao/alarm/AlarmOperationResult.java | 19 ++- .../server/dao/entity/EntityService.java | 4 + .../server/common/data/ContactBased.java | 2 +- .../server/common/data/Customer.java | 2 +- .../server/common/data/DashboardInfo.java | 2 +- .../server/common/data/Device.java | 2 +- .../server/common/data/EntityType.java | 22 ++- .../server/common/data/HasEmail.java} | 16 +- .../server/common/data/HasLabel.java | 22 +++ .../server/common/data/HasTitle.java | 22 +++ .../server/common/data/OtaPackageInfo.java | 2 +- .../server/common/data/Tenant.java | 2 +- .../server/common/data/alarm/AlarmInfo.java | 15 +- .../server/common/data/asset/Asset.java | 3 +- .../server/common/data/edge/Edge.java | 3 +- .../server/common/data/query/AlarmData.java | 4 +- .../common/data/widget/BaseWidgetType.java | 3 +- .../common/data/widget/WidgetsBundle.java | 3 +- .../server/dao/alarm/BaseAlarmService.java | 139 ++++-------------- .../server/dao/entity/BaseEntityService.java | 26 ++++ .../query/DefaultAlarmQueryRepository.java | 37 ++++- .../dao/service/BaseAlarmServiceTest.java | 30 ++-- .../rule/engine/profile/AlarmState.java | 2 +- 32 files changed, 260 insertions(+), 228 deletions(-) rename common/{dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java => data/src/main/java/org/thingsboard/server/common/data/HasEmail.java} (62%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index c2acd77953..c106090508 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -503,14 +503,13 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService { if (TbSubscriptionType.ALARMS.equals(s.getType())) { @@ -302,16 +302,15 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return null; } }, - s -> alarm.getCreatedTime() >= s.getTs(), - s -> alarm, - alarmInfo, + s -> alarmInfo.getCreatedTime() >= 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())) { @@ -320,9 +319,8 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return null; } }, - s -> alarm.getCreatedTime() >= s.getTs(), - s -> alarm, - null, + s -> alarmInfo.getCreatedTime() >= s.getTs(), + s -> alarmInfo, true ); callback.onSuccess(); @@ -417,19 +415,19 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene private void onLocalAlarmSubUpdate(EntityId entityId, Function castFunction, Predicate filterFunction, - Function processFunction, AlarmInfo alarmInfo, + Function processFunction, boolean deleted) { Set 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, alarmInfo, 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, alarmInfo, deleted), null); + toCoreNotificationsProducer.send(tpi, toProto(s, alarmInfo, deleted), null); } } }); @@ -566,12 +564,11 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg); } - private TbProtoQueueMsg toProto(TbSubscription subscription, Alarm alarm, AlarmInfo alarmInfo, boolean deleted) { + private TbProtoQueueMsg 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.setAlarmInfo(JacksonUtil.toString(alarmInfo)); builder.setDeleted(deleted); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java index 747a73ebe1..63c8c9aa6e 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java @@ -16,7 +16,6 @@ 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; @@ -43,9 +42,9 @@ public interface SubscriptionManagerService extends ApplicationListener keys, TbCallback callback); - void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, AlarmInfo alarmInfo, 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); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java index 7afc82c03b..706cbcb8ad 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java @@ -63,10 +63,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { private final AlarmService alarmService; @Getter - @Setter private final LinkedHashMap entitiesMap; @Getter - @Setter private final HashMap alarmsMap; private final int maxEntitiesPerAlarmSubscription; @@ -207,8 +205,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { } private void sendWsMsg(String sessionId, AlarmSubscriptionUpdate subscriptionUpdate) { - Alarm alarm = subscriptionUpdate.getAlarm(); - AlarmId alarmId = alarm.getId(); + AlarmInfo alarmInfo = subscriptionUpdate.getAlarmInfo(); + AlarmId alarmId = alarmInfo.getId(); if (subscriptionUpdate.isAlarmDeleted()) { Alarm deleted = alarmsMap.remove(alarmId); if (deleted != null) { @@ -217,10 +215,10 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { } 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, subscriptionUpdate.getAlarmInfo(), 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())); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java index 91583e1194..7972f6f833 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java @@ -190,9 +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); AlarmInfo alarmInfo = JacksonUtil.fromString(proto.getAlarmInfo(), AlarmInfo.class); - return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm, alarmInfo); + return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarmInfo); } } @@ -318,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.setAlarmInfo(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.setAlarmInfo(JacksonUtil.toString(alarmInfo)); SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); msgBuilder.setAlarmDelete(builder); return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 634c90880a..9ab3858730 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -95,7 +95,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 @@ -181,24 +181,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()) { - AlarmInfo alarmInfo = new AlarmInfo(alarm); - alarmInfo.setOriginatorName(result.getAlarmAdditionalInfo().getOriginatorName()); - alarmInfo.setOriginatorLabel(result.getAlarmAdditionalInfo().getOriginatorName()); - alarmInfo.setAssigneeFirstName(result.getAlarmAdditionalInfo().getFirstName()); - alarmInfo.setAssigneeLastName(result.getAlarmAdditionalInfo().getLastName()); - alarmInfo.setAssigneeEmail(result.getAlarmAdditionalInfo().getEmail()); - subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, alarmInfo, 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); } } @@ -207,18 +201,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); } } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java index 10b750c5c7..47eb7c69bf 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java @@ -37,20 +37,17 @@ public class AlarmSubscriptionUpdate { @Getter private String errorMsg; @Getter - private Alarm alarm; - @Getter private AlarmInfo alarmInfo; @Getter private boolean alarmDeleted; - public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, AlarmInfo alarmInfo) { - this(subscriptionId, alarm, alarmInfo, false); + public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarmInfo) { + this(subscriptionId, alarmInfo, false); } - public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, AlarmInfo alarmInfo, boolean alarmDeleted) { + public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarmInfo, boolean alarmDeleted) { super(); this.subscriptionId = subscriptionId; - this.alarm = alarm; this.alarmInfo = alarmInfo; this.alarmDeleted = alarmDeleted; } @@ -68,7 +65,7 @@ public class AlarmSubscriptionUpdate { @Override public String toString() { - return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", alarm=" - + alarm + ", alarmInfo=" + alarmInfo + "]"; + return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + + ", alarmInfo=" + alarmInfo + "]"; } } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java index e178055706..cd6fbb7eb5 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java +++ b/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 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(); diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 4ef017715d..301c395a18 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -560,9 +560,8 @@ message TbAlarmSubscriptionUpdateProto { int32 subscriptionId = 2; int32 errorCode = 3; string errorMsg = 4; - string alarm = 5; + string alarmInfo = 5; bool deleted = 6; - string alarmInfo = 7; } message TbAttributeUpdateProto { @@ -581,8 +580,7 @@ message TbAlarmUpdateProto { int64 entityIdLSB = 3; int64 tenantIdMSB = 4; int64 tenantIdLSB = 5; - string alarm = 6; - string alarmInfo = 7; + string alarmInfo = 6; } message TbAlarmDeleteProto { @@ -591,7 +589,7 @@ message TbAlarmDeleteProto { int64 entityIdLSB = 3; int64 tenantIdMSB = 4; int64 tenantIdLSB = 5; - string alarm = 6; + string alarmInfo = 6; } message TbAttributeDeleteProto { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java index c8d2c0aad9..10fac5de08 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm; import lombok.Data; 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 java.util.Collections; @@ -24,29 +25,27 @@ import java.util.List; @Data public class AlarmOperationResult { - private final Alarm alarm; private final boolean successful; private final boolean created; private final List propagatedEntitiesList; - private final AlarmAdditionalInfo alarmAdditionalInfo; + private final AlarmInfo alarmInfo; public AlarmOperationResult(Alarm alarm, boolean successful) { - this(alarm, successful, Collections.emptyList(), new AlarmAdditionalInfo(null, null, null, null, null)); + this(new AlarmInfo(alarm, null, null, null, null, null), successful, Collections.emptyList()); } - public AlarmOperationResult(Alarm alarm, boolean successful, AlarmAdditionalInfo alarmAdditionalInfo) { - this(alarm, successful, Collections.emptyList(), alarmAdditionalInfo); + public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful) { + this(alarmInfo, successful, Collections.emptyList()); } - public AlarmOperationResult(Alarm alarm, boolean successful, List propagatedEntitiesList, AlarmAdditionalInfo alarmAdditionalInfo) { - this(alarm, successful, false, propagatedEntitiesList, alarmAdditionalInfo); + public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful, List propagatedEntitiesList) { + this(alarmInfo, successful, false, propagatedEntitiesList); } - public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List propagatedEntitiesList, AlarmAdditionalInfo alarmAdditionalInfo) { - this.alarm = alarm; + public AlarmOperationResult(AlarmInfo alarmInfo, boolean successful, boolean created, List propagatedEntitiesList) { + this.alarmInfo = alarmInfo; this.successful = successful; this.created = created; this.propagatedEntitiesList = propagatedEntitiesList; - this.alarmAdditionalInfo = alarmAdditionalInfo; } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java index 986da96d46..e819420e9d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java @@ -24,10 +24,14 @@ import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataQuery; +import java.util.Optional; + public interface EntityService { ListenableFuture fetchEntityNameAsync(TenantId tenantId, EntityId entityId); + Optional fetchEntityLabel(TenantId tenantId, EntityId entityId); + CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId); long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java b/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java index 5ff7b874b3..9e17b72a5b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java +++ b/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 extends SearchTextBasedWithAdditionalInfo implements HasName { +public abstract class ContactBased extends SearchTextBasedWithAdditionalInfo implements HasEmail { private static final long serialVersionUID = 5047448057830660988L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java index bdd06606a6..9a2a409bcd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java +++ b/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 implements HasTenantId, ExportableEntity { +public class Customer extends ContactBased implements HasTenantId, ExportableEntity, HasTitle { private static final long serialVersionUID = -1599722990298929275L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java index 1895b41639..9f25272433 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java +++ b/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 implements HasName, HasTenantId { +public class DashboardInfo extends SearchTextBased implements HasName, HasTenantId, HasTitle { private TenantId tenantId; @NoXss diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index 4a6589d72c..02f56a4e90 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/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 implements HasName, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity { +public class Device extends SearchTextBasedWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity { private static final long serialVersionUID = 2807343040519543363L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 0798647903..74e03a50d6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/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 } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java similarity index 62% rename from common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java rename to common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java index a726ca599a..7f413075f8 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmAdditionalInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java @@ -13,20 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.alarm; +package org.thingsboard.server.common.data; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; +public interface HasEmail extends HasName { -@Builder -@Getter -@AllArgsConstructor -public class AlarmAdditionalInfo { - private final String originatorName; - private final String originatorLabel; + String getEmail(); - private final String firstName; - private final String lastName; - private final String email; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java new file mode 100644 index 0000000000..d9c3c9b98c --- /dev/null +++ b/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(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java new file mode 100644 index 0000000000..886cef5fb1 --- /dev/null +++ b/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(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java index b31d0a6bc0..b1e8c9eb65 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java +++ b/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 implements HasName, HasTenantId { +public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasTitle { private static final long serialVersionUID = 3168391583570815419L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java index 8d5a9fe3fc..68ad28eece 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java +++ b/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 implements HasTenantId { +public class Tenant extends ContactBased implements HasTenantId, HasTitle { private static final long serialVersionUID = 8057243243859922101L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index 9b55324800..878edb856e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java @@ -61,6 +61,15 @@ public class AlarmInfo extends Alarm { super(alarm); } + 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; + } + public AlarmInfo(Alarm alarm, AlarmInfo alarmInfo) { super(alarm); originatorName = alarmInfo.originatorName; @@ -88,7 +97,11 @@ public class AlarmInfo extends Alarm { @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; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java index e8eeaf75e9..46de963b66 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java +++ b/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 implements HasName, HasTenantId, HasCustomerId, ExportableEntity { +public class Asset extends SearchTextBasedWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, ExportableEntity { private static final long serialVersionUID = 2807343040519543363L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java index 58c23d0877..7ad50a90cf 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java +++ b/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 implements HasName, HasTenantId, HasCustomerId { +public class Edge extends SearchTextBasedWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId { private static final long serialVersionUID = 4934987555236873728L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java index cae512af18..e7f22f2044 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java @@ -31,8 +31,8 @@ public class AlarmData extends AlarmInfo { @Getter private final Map> latest; - public AlarmData(Alarm alarm, AlarmInfo alarmInfo, EntityId entityId) { - super(alarm, alarmInfo); + public AlarmData(AlarmInfo alarmInfo, EntityId entityId) { + super(alarmInfo); this.entityId = entityId; this.latest = new HashMap<>(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java index be5014a4cd..c165e3443a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java +++ b/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 implements HasTenantId { +public class BaseWidgetType extends BaseData implements HasName, HasTenantId { private static final long serialVersionUID = 8388684344603660756L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java index eca19f0db5..d862e67da1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java +++ b/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 implements HasName, HasTenantId, ExportableEntity { +public class WidgetsBundle extends SearchTextBased implements HasName, HasTenantId, ExportableEntity, HasTitle { private static final long serialVersionUID = -7627368878362410489L; diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 1ccac7a19b..4fc5075fb3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -27,11 +27,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EntityView; -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; @@ -40,15 +35,10 @@ import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.EntityAlarm; -import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException; import org.thingsboard.server.common.data.id.AlarmId; -import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; @@ -58,15 +48,9 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; -import org.thingsboard.server.dao.asset.AssetService; -import org.thingsboard.server.dao.customer.CustomerService; -import org.thingsboard.server.dao.dashboard.DashboardService; -import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import javax.annotation.Nullable; @@ -102,24 +86,6 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Autowired private UserService userService; - @Autowired - private TenantService tenantService; - - @Autowired - private CustomerService customerService; - - @Autowired - private DashboardService dashboardService; - - @Autowired - private AssetService assetService; - - @Autowired - private DeviceService deviceService; - - @Autowired - private EntityViewService entityViewService; - @Autowired private DataValidator alarmDataValidator; @@ -190,8 +156,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ if (alarm == null) { return new AlarmOperationResult(alarm, false); } - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); - AlarmOperationResult result = new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); + 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; @@ -201,8 +167,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ log.debug("New Alarm : {}", alarm); Alarm saved = alarmDao.save(alarm.getTenantId(), alarm); List propagatedEntitiesList = createEntityAlarmRecords(saved); - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(alarm.getTenantId(), alarm); - return new AlarmOperationResult(saved, true, true, propagatedEntitiesList, alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(alarm.getTenantId(), alarm); + return new AlarmOperationResult(alarmInfo, true, true, propagatedEntitiesList); } private List createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException { @@ -257,8 +223,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } else { propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result)); } - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(newAlarm.getTenantId(), newAlarm); - return new AlarmOperationResult(result, true, propagatedEntitiesList, alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(newAlarm.getTenantId(), newAlarm); + return new AlarmOperationResult(alarmInfo, true, propagatedEntitiesList); } @Override @@ -275,8 +241,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setStatus(newStatus); alarm.setAckTs(ackTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); + return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm))); } } }); @@ -299,8 +265,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setDetails(details); } alarm = alarmDao.save(alarm.getTenantId(), alarm); - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); + return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm))); } } }); @@ -318,8 +284,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssigneeId(assigneeId); alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); + return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm))); } } }); @@ -337,8 +303,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssigneeId(null); alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - AlarmAdditionalInfo alarmAdditionalInfo = getAlarmAdditionalInfo(tenantId, alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)), alarmAdditionalInfo); + AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); + return new AlarmOperationResult(alarmInfo, true, new ArrayList<>(getPropagationEntityIds(alarm))); } } }); @@ -362,15 +328,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ public ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) { log.trace("Executing findAlarmInfoByIdAsync [{}]", alarmId); validateId(alarmId, "Incorrect alarmId " + alarmId); - return Futures.transformAsync(alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()), - a -> { - AlarmInfo alarmInfo = new AlarmInfo(a); - return Futures.transform( - entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> { - alarmInfo.setOriginatorName(originatorName); - return alarmInfo; - }, MoreExecutors.directExecutor()); - }, MoreExecutors.directExecutor()); + return Futures.transform(alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()), + a -> getAlarmInfo(tenantId, a), MoreExecutors.directExecutor()); } @Override @@ -478,7 +437,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ private void createEntityAlarmRecord(TenantId tenantId, EntityId entityId, Alarm alarm) { // TODO Add ability to automatically assign created alarm to some user - EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), null,alarm.getId()); + EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), null, alarm.getId()); try { alarmDao.createEntityAlarmRecord(entityAlarm); } catch (Exception e) { @@ -498,62 +457,22 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ return function.apply(entity); } - private AlarmAdditionalInfo getAlarmAdditionalInfo(TenantId tenantId, Alarm alarm) { - AlarmAdditionalInfo.AlarmAdditionalInfoBuilder builder = AlarmAdditionalInfo.builder(); + private AlarmInfo getAlarmInfo(TenantId tenantId, Alarm alarm) { + String originatorName = null; + String originatorLabel = null; + String assigneeFirstName = null; + String assigneeLastName = null; + String assigneeEmail = null; - addAlarmOriginatorNameAndLabel(tenantId, alarm.getOriginator(), builder); + originatorName = entityService.fetchEntityName(tenantId, alarm.getOriginator()); + originatorLabel = entityService.fetchEntityLabel(tenantId, alarm.getOriginator()); if (alarm.getAssigneeId() != null) { User assignedUser = userService.findUserById(tenantId, alarm.getAssigneeId()); - builder.firstName(assignedUser.getFirstName()); - builder.lastName(assignedUser.getLastName()); - builder.email(assignedUser.getEmail()); + assigneeFirstName = assignedUser.getFirstName(); + assigneeLastName = assignedUser.getLastName(); + assigneeEmail = assignedUser.getEmail(); } - return builder.build(); - } - - private void addAlarmOriginatorNameAndLabel(TenantId tenantId, EntityId originatorId, AlarmAdditionalInfo.AlarmAdditionalInfoBuilder builder) { - String originatorName = "Unknown"; - String originatorLabel = "Unknown"; - switch(originatorId.getEntityType()) { - case TENANT: - Tenant tenantOriginator = tenantService.findTenantById((TenantId) originatorId); - originatorName = tenantOriginator.getTitle(); - originatorLabel = tenantOriginator.getEmail(); - break; - case CUSTOMER: - Customer customerOriginator = customerService.findCustomerById(tenantId, (CustomerId) originatorId); - originatorName = customerOriginator.getTitle(); - originatorLabel = customerOriginator.getEmail(); - break; - case USER: - User userOriginator = userService.findUserById(tenantId, (UserId) originatorId); - originatorName = userOriginator.getEmail(); - originatorLabel = userOriginator.getName(); - break; - case DASHBOARD: - Dashboard dashboardOriginator = dashboardService.findDashboardById(tenantId, (DashboardId) originatorId); - originatorName = dashboardOriginator.getTitle(); - originatorLabel = dashboardOriginator.getName(); - break; - case ASSET: - Asset assetOriginator = assetService.findAssetById(tenantId, (AssetId) originatorId); - originatorName = assetOriginator.getName(); - originatorLabel = assetOriginator.getLabel(); - break; - case DEVICE: - Device deviceOriginator = deviceService.findDeviceById(tenantId, (DeviceId) originatorId); - originatorName = deviceOriginator.getName(); - originatorLabel = deviceOriginator.getLabel(); - break; - case ENTITY_VIEW: - EntityView entityViewOriginator = entityViewService.findEntityViewById(tenantId, (EntityViewId) originatorId); - originatorName = entityViewOriginator.getName(); - originatorLabel = entityViewOriginator.getType(); // TODO Should we use something else? - break; - } - - builder.originatorName(originatorName); - builder.originatorLabel(originatorLabel); + return new AlarmInfo(alarm, originatorName, originatorLabel, assigneeFirstName, assigneeLastName, assigneeEmail); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index 49cea353fb..0f74f13fd9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -24,7 +24,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.AlarmId; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; @@ -33,6 +36,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TbResourceId; @@ -58,6 +62,8 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; +import java.util.Optional; + import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -175,6 +181,26 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe return entityName; } + @Override + public Optional fetchEntityLabel(TenantId tenantId, EntityId entityId) { + return Optional.empty(); + HasId entity = fetchEntity(tenantId, entityId); + String entityLabel = null; + 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 CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId) { log.trace("Executing fetchEntityCustomerId [{}]", entityId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index d3b8137f8b..0e0bf23f45 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -69,7 +69,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { alarmFieldColumnMap.put("originatorId", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); alarmFieldColumnMap.put("originatorType", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY); alarmFieldColumnMap.put("assigneeId", ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY); - alarmFieldColumnMap.put("originator", "originator_name"); + 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); @@ -91,7 +91,36 @@ 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," + @@ -115,7 +144,9 @@ 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"; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java index ba22a57110..0d0503275d 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java @@ -89,7 +89,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarm(); + Alarm created = result.getAlarmInfo(); Assert.assertNotNull(created); Assert.assertNotNull(created.getId()); @@ -128,7 +128,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarm(); + Alarm created = result.getAlarmInfo(); // Check child relation PageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() @@ -153,7 +153,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { created.setPropagate(true); result = alarmService.createOrUpdateAlarm(created); - created = result.getAlarm(); + created = result.getAlarmInfo(); // Check child relation alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() @@ -242,7 +242,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 = result.getAlarmInfo(); Alarm deviceAlarm = Alarm.builder().tenantId(tenantId) .originator(customerDevice.getId()) @@ -251,7 +251,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 = result.getAlarmInfo(); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -318,7 +318,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 = result.getAlarmInfo(); Alarm customerAlarm = Alarm.builder().tenantId(tenantId) .originator(tenantDevice.getId()) @@ -327,7 +327,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 = result.getAlarmInfo(); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -368,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 = result.getAlarmInfo(); Alarm customerAlarm = Alarm.builder().tenantId(tenantId) .originator(device.getId()) @@ -378,7 +378,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 = result.getAlarmInfo(); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -435,7 +435,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 +446,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,7 +457,7 @@ 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, null)); Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null, null)); @@ -487,7 +487,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarm(); + Alarm created = result.getAlarmInfo(); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -530,7 +530,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { // Check child relation created.setPropagate(true); result = alarmService.createOrUpdateAlarm(created); - created = result.getAlarm(); + created = result.getAlarmInfo(); // Check child relation pageLink.setPage(0); @@ -645,7 +645,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarm(); + Alarm created = result.getAlarmInfo(); PageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 1f90b4e6c1..bbda497b46 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/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); From 022aee36d0015b88a492637ed9b66e16b3008df2 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 29 Dec 2022 20:49:58 +0200 Subject: [PATCH 07/60] Merge with develop/3.5 --- .../install/update/DefaultDataUpdateService.java | 2 +- .../thingsboard/server/dao/entity/EntityService.java | 2 +- .../server/dao/alarm/BaseAlarmService.java | 6 +++--- .../server/dao/entity/BaseEntityService.java | 12 ++++-------- .../server/dao/model/sql/AbstractAlarmEntity.java | 2 +- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index fae6eed91a..a831a0af5e 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -561,7 +561,7 @@ public class DefaultDataUpdateService implements DataUpdateService { while (hasNext) { for (Alarm alarm : alarms.getData()) { if (alarm.getCustomerId() == null && alarm.getOriginator() != null) { - alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator()).get()); + alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator())); alarmDao.save(tenantId, alarm); } if (processed.incrementAndGet() % 1000 == 0) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java index 59b9d8101f..0ad828acee 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java @@ -31,7 +31,7 @@ public interface EntityService { Optional fetchEntityLabel(TenantId tenantId, EntityId entityId); - Optional fetchEntityCustomerId(TenantId tenantId, EntityId entityId); + CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId); long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index a2ddf79367..3412e79085 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -122,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())); if (alarm.getId() == null) { Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()); if (existing == null || existing.getStatus().isCleared()) { @@ -172,7 +172,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ log.debug("New Alarm : {}", alarm); Alarm saved = alarmDao.save(alarm.getTenantId(), alarm); List propagatedEntitiesList = createEntityAlarmRecords(saved); - AlarmInfo alarmInfo = getAlarmInfo(alarm.getTenantId(), alarm); + AlarmInfo alarmInfo = getAlarmInfo(alarm.getTenantId(), saved); return new AlarmOperationResult(alarmInfo, true, true, propagatedEntitiesList); } @@ -463,7 +463,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ String assigneeLastName = null; String assigneeEmail = null; - originatorName = entityService.fetchEntityName(tenantId, alarm.getOriginator()).orElse("Deleted"); + originatorName = entityService.fetchEntityName(tenantId, alarm.getOriginator()).orElse(null); originatorLabel = entityService.fetchEntityLabel(tenantId, alarm.getOriginator()).orElse(null); if (alarm.getAssigneeId() != null) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index b0c90315b4..34175edb5a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -96,7 +96,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe public Optional fetchEntityLabel(TenantId tenantId, EntityId entityId) { log.trace("Executing fetchEntityLabel [{}]", entityId); EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType()); - Optional> entityOpt = entityDaoService.fetchEntity(tenantId, entityId); + Optional> entityOpt = entityDaoService.findEntity(tenantId, entityId); String entityLabel = null; if (entityOpt.isPresent()) { HasId entity = entityOpt.get(); @@ -117,7 +117,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe } @Override - public Optional fetchEntityCustomerId(TenantId tenantId, EntityId entityId) { + public CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId) { log.trace("Executing fetchEntityCustomerId [{}]", entityId); EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType()); Optional> hasIdOpt = entityDaoService.findEntity(tenantId, entityId); @@ -125,14 +125,10 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe HasId hasId = hasIdOpt.get(); if (hasId instanceof HasCustomerId) { HasCustomerId hasCustomerId = (HasCustomerId) hasId; - CustomerId customerId = hasCustomerId.getCustomerId(); - if (customerId == null) { - customerId = NULL_CUSTOMER_ID; - } - return Optional.of(customerId); + return hasCustomerId.getCustomerId(); } } - return Optional.of(NULL_CUSTOMER_ID); + return null; } private static void validateEntityCountQuery(EntityCountQuery query) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java index c01b145174..64006d0630 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java @@ -163,7 +163,7 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity if (!CollectionUtils.isEmpty(alarm.getPropagateRelationTypes())) { this.propagateRelationTypes = String.join(",", alarm.getPropagateRelationTypes()); } else { - this.propagateRelationTypes = null; + this.propagateRelationTypes = ""; } } From a812a88db1fd5a6e42c81e6be3a5ad0cbe5e5eb1 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 30 Dec 2022 12:51:45 +0200 Subject: [PATCH 08/60] Refactoring to keep EntityService --- .../install/update/DefaultDataUpdateService.java | 2 +- .../thingsboard/server/dao/entity/EntityService.java | 2 +- .../thingsboard/server/dao/alarm/BaseAlarmService.java | 2 +- .../server/dao/entity/BaseEntityService.java | 10 +++++++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index a831a0af5e..fae6eed91a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -561,7 +561,7 @@ public class DefaultDataUpdateService implements DataUpdateService { while (hasNext) { for (Alarm alarm : alarms.getData()) { if (alarm.getCustomerId() == null && alarm.getOriginator() != null) { - alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator())); + alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator()).get()); alarmDao.save(tenantId, alarm); } if (processed.incrementAndGet() % 1000 == 0) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java index 0ad828acee..59b9d8101f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java @@ -31,7 +31,7 @@ public interface EntityService { Optional fetchEntityLabel(TenantId tenantId, EntityId entityId); - CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId); + Optional fetchEntityCustomerId(TenantId tenantId, EntityId entityId); long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 3412e79085..819c2b79b5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -122,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())); + 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()) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index 34175edb5a..de3810d102 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -117,7 +117,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe } @Override - public CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId) { + public Optional fetchEntityCustomerId(TenantId tenantId, EntityId entityId) { log.trace("Executing fetchEntityCustomerId [{}]", entityId); EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType()); Optional> hasIdOpt = entityDaoService.findEntity(tenantId, entityId); @@ -125,10 +125,14 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe HasId hasId = hasIdOpt.get(); if (hasId instanceof HasCustomerId) { HasCustomerId hasCustomerId = (HasCustomerId) hasId; - return hasCustomerId.getCustomerId(); + CustomerId customerId = hasCustomerId.getCustomerId(); + if (customerId == null) { + customerId = NULL_CUSTOMER_ID; + } + return Optional.of(customerId); } } - return null; + return Optional.of(NULL_CUSTOMER_ID); } private static void validateEntityCountQuery(EntityCountQuery query) { From 5df42a591a629753bec6528d03f52cf22478e14b Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 4 Jan 2023 08:27:42 +0200 Subject: [PATCH 09/60] Updated tests for alarms to compare correctly returned value --- .../server/dao/alarm/BaseAlarmService.java | 2 +- .../query/DefaultAlarmQueryRepository.java | 6 +-- .../dao/service/BaseAlarmServiceTest.java | 50 +++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 819c2b79b5..a94cca8a81 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -463,7 +463,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ String assigneeLastName = null; String assigneeEmail = null; - originatorName = entityService.fetchEntityName(tenantId, alarm.getOriginator()).orElse(null); + originatorName = entityService.fetchEntityName(tenantId, alarm.getOriginator()).orElse("Deleted"); originatorLabel = entityService.fetchEntityLabel(tenantId, alarm.getOriginator()).orElse(null); if (alarm.getAssigneeId() != null) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index 0e0bf23f45..71af76ce5d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -144,13 +144,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," + + " 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 LEFT_JOIN_TB_USERS = "left join tb_user tbu on a.assignee_id = tbu.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; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java index 0d0503275d..695940ece8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java @@ -89,7 +89,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarmInfo(); + Alarm created = new Alarm(result.getAlarmInfo()); Assert.assertNotNull(created); Assert.assertNotNull(created.getId()); @@ -128,7 +128,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarmInfo(); + Alarm created = new Alarm(result.getAlarmInfo()); // Check child relation PageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() @@ -139,7 +139,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 +153,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { created.setPropagate(true); result = alarmService.createOrUpdateAlarm(created); - created = result.getAlarmInfo(); + created = new Alarm(result.getAlarmInfo()); // Check child relation alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() @@ -164,7 +164,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 +175,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 +188,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,7 +211,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))); } @Test @@ -242,7 +242,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm); - tenantAlarm = result.getAlarmInfo(); + tenantAlarm = new Alarm(result.getAlarmInfo()); Alarm deviceAlarm = Alarm.builder().tenantId(tenantId) .originator(customerDevice.getId()) @@ -251,7 +251,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); result = alarmService.createOrUpdateAlarm(deviceAlarm); - deviceAlarm = result.getAlarmInfo(); + deviceAlarm = new Alarm(result.getAlarmInfo()); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -279,7 +279,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 +318,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm); - tenantAlarm = result.getAlarmInfo(); + tenantAlarm = new Alarm(result.getAlarmInfo()); Alarm customerAlarm = Alarm.builder().tenantId(tenantId) .originator(tenantDevice.getId()) @@ -327,7 +327,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); result = alarmService.createOrUpdateAlarm(customerAlarm); - customerAlarm = result.getAlarmInfo(); + customerAlarm = new Alarm(result.getAlarmInfo()); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -368,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.getAlarmInfo(); + tenantAlarm = new Alarm(result.getAlarmInfo()); Alarm customerAlarm = Alarm.builder().tenantId(tenantId) .originator(device.getId()) @@ -378,7 +378,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); result = alarmService.createOrUpdateAlarm(customerAlarm); - customerAlarm = result.getAlarmInfo(); + customerAlarm = new Alarm(result.getAlarmInfo()); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -487,7 +487,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarmInfo(); + Alarm created = new Alarm(result.getAlarmInfo()); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -504,7 +504,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 +530,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { // Check child relation created.setPropagate(true); result = alarmService.createOrUpdateAlarm(created); - created = result.getAlarmInfo(); + created = new Alarm(result.getAlarmInfo()); // Check child relation pageLink.setPage(0); @@ -546,7 +546,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 +562,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 alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) @@ -607,7 +607,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 +625,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 +645,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = result.getAlarmInfo(); + Alarm created = new Alarm(result.getAlarmInfo()); PageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) @@ -655,7 +655,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 +666,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()); From 91b30f3ce7a09503263404bfa0543e871a832c6a Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 4 Jan 2023 11:14:06 +0200 Subject: [PATCH 10/60] Updated upgrade script --- .../main/data/upgrade/{3.4.2 => 3.4.3}/schema_update.sql | 0 .../server/service/install/SqlDatabaseUpgradeService.java | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename application/src/main/data/upgrade/{3.4.2 => 3.4.3}/schema_update.sql (100%) diff --git a/application/src/main/data/upgrade/3.4.2/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql similarity index 100% rename from application/src/main/data/upgrade/3.4.2/schema_update.sql rename to application/src/main/data/upgrade/3.4.3/schema_update.sql diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 349400ecd1..f76178455b 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -677,13 +677,13 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.error("Failed updating schema!!!", e); } break; - case "3.4.2": + case "3.4.3": try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.2", SCHEMA_UPDATE_SQL); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.3", SCHEMA_UPDATE_SQL); loadSql(schemaUpdateFile, conn); log.info("Updating schema settings..."); - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004003;"); + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004004;"); log.info("Schema updated."); } catch (Exception e) { log.error("Failed updating schema!!!", e); From 5677d61f4fe3651ba6e6fe54858b0fface746844 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 4 Jan 2023 12:50:43 +0200 Subject: [PATCH 11/60] Added returning alarm instead of alarmInfo for controllers and alarmState service --- .../service/telemetry/DefaultAlarmSubscriptionService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 9ab3858730..d6aa9cba26 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -95,7 +95,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService if (result.isCreated()) { apiUsageClient.report(alarm.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT); } - return result.getAlarmInfo(); + return new Alarm(result.getAlarmInfo()); } @Override From 5700fe630d7c89d3d1c900af7ce4b7180c88306c Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 5 Jan 2023 11:35:46 +0200 Subject: [PATCH 12/60] Updated security check and tests --- .../server/controller/AlarmController.java | 15 ++++--- .../server/controller/AbstractWebTest.java | 4 +- .../controller/BaseAlarmControllerTest.java | 45 +++++++++++++++++++ 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index fd91abed3a..26e61a495a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/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; @@ -47,6 +48,7 @@ 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; @@ -204,12 +206,13 @@ public class AlarmController extends BaseController { AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); UserId assigneeId = new UserId(UUID.fromString(strAssigneeId)); Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); - if (!getCurrentUser().getId().equals(assigneeId)) { - try { - checkEntityId(alarm.getOriginator(), Operation.WRITE, new SecurityUser(assigneeId)); - } catch (Exception e) { - throw new ThingsboardException("Assignee user doesn't have permission for alarm originator", ThingsboardErrorCode.PERMISSION_DENIED); - } + User assigneeUser = userService.findUserById(getTenantId(), assigneeId); + + SecurityUser assigneeSecurityUser = new SecurityUser(assigneeUser, false, null); + try { + checkEntityId(alarm.getOriginator(), Operation.WRITE, assigneeSecurityUser); + } catch (Exception e) { + throw new ThingsboardException("Assignee user doesn't have permission for alarm originator", ThingsboardErrorCode.PERMISSION_DENIED); } tbAlarmService.assign(alarm, getCurrentUser(), assigneeId).get(); } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 9ea269918e..e94a0646d9 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -154,6 +154,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { protected TenantId differentTenantId; protected CustomerId differentCustomerId; protected UserId customerUserId; + protected UserId differentCustomerUserId; @SuppressWarnings("rawtypes") private HttpMessageConverter mappingJackson2HttpMessageConverter; @@ -324,7 +325,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(); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java index 05b6f15c61..cc793d8984 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java @@ -28,6 +28,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; @@ -364,6 +365,18 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { 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(); @@ -426,6 +439,38 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { 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(); From a657f9fcea5174161b13fdc4d37c44e4203bfb50 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 6 Jan 2023 12:24:34 +0200 Subject: [PATCH 13/60] Added getting assinment info to findAlarms methods --- .../server/dao/alarm/BaseAlarmService.java | 5 +- .../server/dao/model/sql/AlarmInfoEntity.java | 6 +- .../server/dao/sql/alarm/AlarmRepository.java | 12 +++- .../dao/service/BaseAlarmServiceTest.java | 58 +++++++++++++++++-- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index a94cca8a81..b0f904e71b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -27,8 +27,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.User; 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; @@ -360,6 +360,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), @@ -435,7 +437,6 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } private void createEntityAlarmRecord(TenantId tenantId, EntityId entityId, Alarm alarm) { - // TODO Add ability to automatically assign created alarm to some user EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), null, alarm.getId()); try { alarmDao.createEntityAlarmRecord(entityAlarm); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java index aada0c7c17..2e28c1407a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java @@ -34,8 +34,12 @@ public class AlarmInfoEntity extends AbstractAlarmEntity { 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 diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index 24bfe88656..4fc8237661 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -39,8 +39,10 @@ public interface AlarmRepository extends JpaRepository { @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 ea.assigneeId = tbu.id " + "WHERE a.tenantId = :tenantId " + "AND ea.tenantId = :tenantId " + "AND ea.entityId = :affectedEntityId " + @@ -78,7 +80,9 @@ public interface AlarmRepository extends JpaRepository { @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 a.assigneeId = tbu.id " + "WHERE a.tenantId = :tenantId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + @@ -106,7 +110,9 @@ public interface AlarmRepository extends JpaRepository { @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 a.assigneeId = tbu.id " + "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java index 695940ece8..cff9b81721 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java +++ b/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 @@ -214,6 +220,50 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { 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).get(); + created = assignmentResult.getAlarmInfo(); + + PageData 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)); + } + @Test public void testFindCustomerAlarm() throws ExecutionException, InterruptedException { Customer customer = new Customer(); @@ -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 alarmFields){ + private AlarmDataQuery toQuery(AlarmDataPageLink pageLink, List alarmFields) { return new AlarmDataQuery(new DeviceTypeFilter(), pageLink, null, null, null, alarmFields); } From c99316976a2d376e1aa056fc1c4eb323a4b1cacd Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 11 Jan 2023 13:20:20 +0200 Subject: [PATCH 14/60] Updated queries due to particle review --- .../main/data/upgrade/3.4.3/schema_update.sql | 2 -- .../server/dao/sql/alarm/AlarmRepository.java | 19 ++++++++++--------- .../server/dao/sql/alarm/JpaAlarmDao.java | 2 ++ .../resources/sql/schema-entities-idx.sql | 2 -- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index de7cd5b8b1..ca57a80487 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -22,6 +22,4 @@ ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assignee_id UUID; ALTER TABLE entity_alarm ADD COLUMN IF NOT EXISTS assignee_id UUID; -CREATE INDEX IF NOT EXISTS idx_entity_alarm_assignee_id ON entity_alarm(assignee_id); - -- ALARM ASSIGN TO USER END \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index 4fc8237661..5a4d93d7ae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.sql.alarm; +import org.hibernate.type.UUIDCharType; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -42,7 +43,7 @@ public interface AlarmRepository extends JpaRepository { @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 ea.assigneeId = tbu.id " + + "LEFT JOIN UserEntity tbu ON tbu.id = a.assigneeId " + "WHERE a.tenantId = :tenantId " + "AND ea.tenantId = :tenantId " + "AND ea.entityId = :affectedEntityId " + @@ -50,7 +51,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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, '%'))) " @@ -66,7 +67,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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, '%'))) ") @@ -82,12 +83,12 @@ public interface AlarmRepository extends JpaRepository { @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 a.assigneeId = tbu.id " + + "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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, '%'))) ", @@ -98,7 +99,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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, '%'))) ") @@ -112,12 +113,12 @@ public interface AlarmRepository extends JpaRepository { @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 a.assigneeId = tbu.id " + + "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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, '%'))) " @@ -129,7 +130,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))" + + "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (: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, '%'))) ") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index b862f1c7e7..954258a388 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/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; diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index d07650e657..e4c766e81d 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -28,8 +28,6 @@ CREATE INDEX IF NOT EXISTS idx_entity_alarm_created_time ON entity_alarm(tenant_ CREATE INDEX IF NOT EXISTS idx_entity_alarm_alarm_id ON entity_alarm(alarm_id); -CREATE INDEX IF NOT EXISTS idx_entity_alarm_assignee_id ON entity_alarm(assignee_id); - CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id); CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id); From 37bc889f11fd89325d29d1bcb31de87f181316ab Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 11 Jan 2023 14:51:57 +0200 Subject: [PATCH 15/60] Updated queries --- .../server/dao/sql/alarm/AlarmRepository.java | 21 +++++++++---------- .../server/dao/sql/alarm/JpaAlarmDao.java | 8 +++---- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index 5a4d93d7ae..7320451cd5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.sql.alarm; -import org.hibernate.type.UUIDCharType; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -51,7 +50,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId)) " + + "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, '%'))) " @@ -67,7 +66,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId)) " + + "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, '%'))) ") @@ -77,7 +76,7 @@ public interface AlarmRepository extends JpaRepository { @Param("startTime") Long startTime, @Param("endTime") Long endTime, @Param("alarmStatuses") Set alarmStatuses, - @Param("assigneeId") UUID assigneeId, + @Param("assigneeId") String assigneeId, @Param("searchText") String searchText, Pageable pageable); @@ -88,7 +87,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId)) " + + "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, '%'))) ", @@ -99,7 +98,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId)) " + + "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, '%'))) ") @@ -107,7 +106,7 @@ public interface AlarmRepository extends JpaRepository { @Param("startTime") Long startTime, @Param("endTime") Long endTime, @Param("alarmStatuses") Set alarmStatuses, - @Param("assigneeId") UUID assigneeId, + @Param("assigneeId") String assigneeId, @Param("searchText") String searchText, Pageable pageable); @@ -118,7 +117,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId)) " + + "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, '%'))) " @@ -130,7 +129,7 @@ public interface AlarmRepository extends JpaRepository { "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 (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId)) " + + "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, '%'))) ") @@ -139,7 +138,7 @@ public interface AlarmRepository extends JpaRepository { @Param("startTime") Long startTime, @Param("endTime") Long endTime, @Param("alarmStatuses") Set alarmStatuses, - @Param("assigneeId") UUID assigneeId, + @Param("assigneeId") String assigneeId, @Param("searchText") String searchText, Pageable pageable); @@ -150,7 +149,7 @@ public interface AlarmRepository extends JpaRepository { "AND ea.entityId = :affectedEntityId " + "AND ea.entityType = :affectedEntityType " + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + - "AND (cast(:assigneeId as org.hibernate.type.UUIDCharType) IS NULL OR a.assigneeId = (:assigneeId))") + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId))") Set findAlarmSeverities(@Param("tenantId") UUID tenantId, @Param("affectedEntityId") UUID affectedEntityId, @Param("affectedEntityType") String affectedEntityType, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 954258a388..fae28ac916 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -115,9 +115,9 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } else if (query.getStatus() != null) { statusSet = Collections.singleton(query.getStatus()); } - UUID assigneeId = null; + String assigneeId = null; if (query.getAssigneeId() != null) { - assigneeId = query.getAssigneeId().getId(); + assigneeId = query.getAssigneeId().toString(); } if (affectedEntity != null) { return DaoUtil.toPageData( @@ -157,9 +157,9 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } else if (query.getStatus() != null) { statusSet = Collections.singleton(query.getStatus()); } - UUID assigneeId = null; + String assigneeId = null; if (query.getAssigneeId() != null) { - assigneeId = query.getAssigneeId().getId(); + assigneeId = query.getAssigneeId().toString(); } return DaoUtil.toPageData( alarmRepository.findCustomerAlarms( From f9e1077bca23d40233932da29d385b5182483755 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 12 Jan 2023 10:03:04 +0200 Subject: [PATCH 16/60] Updated findSeverity method --- .../org/thingsboard/server/controller/AlarmController.java | 6 +----- .../service/telemetry/DefaultAlarmSubscriptionService.java | 4 ++-- .../java/org/thingsboard/server/dao/alarm/AlarmService.java | 2 +- .../java/org/thingsboard/server/dao/alarm/AlarmDao.java | 2 +- .../org/thingsboard/server/dao/alarm/BaseAlarmService.java | 4 ++-- .../thingsboard/server/dao/sql/alarm/AlarmRepository.java | 2 +- .../org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java | 6 +----- .../thingsboard/rule/engine/api/RuleEngineAlarmService.java | 2 +- 8 files changed, 10 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 26e61a495a..03a7f20c8c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -374,13 +374,9 @@ 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)); - } checkEntityId(entityId, Operation.READ); try { - return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus, assigneeUserId); + return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus, assigneeId); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index d6aa9cba26..dd4b9b6c55 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -165,8 +165,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService } @Override - public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, UserId assigneeUserId) { - return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus, assigneeUserId); + public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId) { + return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus, assigneeId); } @Override diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index a6a61d01c5..848d6dd5b1 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -65,7 +65,7 @@ public interface AlarmService extends EntityDaoService { ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, - AlarmStatus alarmStatus, UserId assigneeUserId); + AlarmStatus alarmStatus, String assigneeId); ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index 11c3e47599..f4bc66d4e4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -59,7 +59,7 @@ public interface AlarmDao extends Dao { PageData findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection orderedEntityIds); - Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set status, UserId assigneeUserId); + Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set status, String assigneeId); PageData findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index b0f904e71b..e1b38a0dd5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -371,7 +371,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, - AlarmStatus alarmStatus, UserId assigneeUserId) { + AlarmStatus alarmStatus, String assigneeId) { Set statusList = null; if (alarmSearchStatus != null) { statusList = alarmSearchStatus.getStatuses(); @@ -379,7 +379,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ statusList = Collections.singleton(alarmStatus); } - Set alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList, assigneeUserId); + Set alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList, assigneeId); return alarmSeverities.stream().min(AlarmSeverity::compareTo).orElse(null); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index 7320451cd5..6b28577851 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -154,7 +154,7 @@ public interface AlarmRepository extends JpaRepository { @Param("affectedEntityId") UUID affectedEntityId, @Param("affectedEntityType") String affectedEntityType, @Param("alarmStatuses") Set alarmStatuses, - @Param("assigneeId") UUID assigneeId); + @Param("assigneeId") String assigneeId); @Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.createdTime < :time AND a.endTs < :time") Page findAlarmsIdsByEndTsBeforeAndTenantId(@Param("time") Long time, @Param("tenantId") UUID tenantId, Pageable pageable); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index fae28ac916..64857269f7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -181,11 +181,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } @Override - public Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set statuses, UserId assigneeUserId) { - UUID assigneeId = null; - if (assigneeUserId != null) { - assigneeId = assigneeUserId.getId(); - } + public Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set statuses, String assigneeId) { return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses, assigneeId); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java index e41c7ad5d5..3afaf677d8 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java @@ -66,7 +66,7 @@ public interface RuleEngineAlarmService { ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); - AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, UserId assigneeId); + AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId); PageData findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection orderedEntityIds); } From 8e79d8f9a21237a1f004db99f3396726d4b8a255 Mon Sep 17 00:00:00 2001 From: rusikv Date: Sun, 15 Jan 2023 13:14:48 +0200 Subject: [PATCH 17/60] Alarm comments UI implementation, alarm details redesign --- .../app/core/http/alarm-comment.service.ts | 46 +++ ui-ngx/src/app/core/services/utils.service.ts | 9 +- .../alarm/alarm-comment-dialog.component.html | 51 ++++ .../alarm/alarm-comment-dialog.component.ts | 60 ++++ .../alarm/alarm-comment.component.html | 144 +++++++++ .../alarm/alarm-comment.component.scss | 106 +++++++ .../alarm/alarm-comment.component.ts | 277 ++++++++++++++++++ .../alarm/alarm-details-dialog.component.html | 92 +++--- .../alarm/alarm-details-dialog.component.scss | 46 +++ .../alarm/alarm-details-dialog.component.ts | 9 +- .../shared-home-components.module.ts | 10 +- .../lib/alarms-table-widget.component.ts | 38 +++ ...larms-table-widget-settings.component.html | 3 + .../alarms-table-widget-settings.component.ts | 1 + ui-ngx/src/app/shared/models/alarm.models.ts | 24 ++ .../app/shared/models/id/alarm-comment-id.ts | 24 ++ ui-ngx/src/app/shared/pipe/date-ago.pipe.ts | 86 ++++++ ui-ngx/src/app/shared/shared.module.ts | 8 +- .../assets/locale/locale.constant-en_US.json | 26 +- 19 files changed, 1012 insertions(+), 48 deletions(-) create mode 100644 ui-ngx/src/app/core/http/alarm-comment.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss create mode 100644 ui-ngx/src/app/shared/models/id/alarm-comment-id.ts create mode 100644 ui-ngx/src/app/shared/pipe/date-ago.pipe.ts diff --git a/ui-ngx/src/app/core/http/alarm-comment.service.ts b/ui-ngx/src/app/core/http/alarm-comment.service.ts new file mode 100644 index 0000000000..f593f83235 --- /dev/null +++ b/ui-ngx/src/app/core/http/alarm-comment.service.ts @@ -0,0 +1,46 @@ +/// +/// 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. +/// + +import { Injectable } from '@angular/core'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { AlarmComment, AlarmCommentInfo } from '@shared/models/alarm.models'; + +@Injectable({ + providedIn: 'root' +}) +export class AlarmCommentService { + + constructor( + private http: HttpClient + ) { } + + public saveAlarmComment(alarmId: string, alarmComment: AlarmComment, config?: RequestConfig): Observable { + return this.http.post(`/api/alarm/${alarmId}/comment`, alarmComment, defaultHttpOptionsFromConfig(config)); + } + + public getAlarmComments(alarmId: string, pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/alarm/${alarmId}/comment${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); + } + + public deleteAlarmComments(alarmId: string, commentId: string, config?: RequestConfig): Observable { + return this.http.delete(`/api/alarm/${alarmId}/comment/${commentId}`, defaultHttpOptionsFromConfig(config)); + } + +} diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 66eefa9e13..1d135c4a6a 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -25,7 +25,7 @@ import { createLabelFromDatasource, deepClone, deleteNullProperties, - guid, + guid, hashCode, isDefined, isDefinedAndNotNull, isString, @@ -405,6 +405,13 @@ export class UtilsService { }); } + public stringToHslColor(str: string, saturationPercentage: number, lightnessPercentage: number): string { + if (str && str.length) { + let hue = hashCode(str) % 360; + return `hsl(${hue}, ${saturationPercentage}%, ${lightnessPercentage}%)`; + } + } + public currentPerfTime(): number { return this.window.performance && this.window.performance.now ? this.window.performance.now() : Date.now(); diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html new file mode 100644 index 0000000000..e38e3da558 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html @@ -0,0 +1,51 @@ + + +
+ +

{{ 'alarm.comments' | translate }}

+ + +
+ + +
+
+ + +
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts new file mode 100644 index 0000000000..f0a4e3c448 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts @@ -0,0 +1,60 @@ +/// +/// 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. +/// + +import { Component, Inject, ViewChild } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Router } from '@angular/router'; +import { AlarmInfo } from '@shared/models/alarm.models'; +import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; + +export interface AlarmCommentDialogData { + alarmId?: string; + alarm?: AlarmInfo; + commentsHeaderEnabled: boolean; +} + +@Component({ + selector: 'tb-alarm-comment-dialog', + templateUrl: './alarm-comment-dialog.component.html', + styleUrls: [] +}) +export class AlarmCommentDialogComponent extends DialogComponent { + + alarmId: string; + commentsHeaderEnabled: boolean = false; + + @ViewChild('alarmCommentComponent', { static: true }) alarmCommentComponent: AlarmCommentComponent; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AlarmCommentDialogData, + public dialogRef: MatDialogRef) { + super(store, router, dialogRef); + this.commentsHeaderEnabled = this.data.commentsHeaderEnabled + this.alarmId = this.data.alarmId; + } + + refresh() { + this.alarmCommentComponent.loadAlarmComments(); + } + + close(): void { + this.dialogRef.close(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html new file mode 100644 index 0000000000..c0d40e744d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html @@ -0,0 +1,144 @@ + + +
+
+ {{ 'alarm-comment.comments' | translate }} + +
+
+
+
+ + {{ displayDataElement.commentText }} + + + {{ displayDataElement.createdDateAgo }} + +
+ +
+
+ {{ getUserInitials(displayDataElement.displayName) }} +
+
+
+ {{ displayDataElement.displayName }} + + edited {{ displayDataElement.editedDateAgo }} + + + {{ displayDataElement.createdDateAgo }} + +
+ {{ displayDataElement.commentText }} +
+
+ + +
+
+ +
+
+ {{ getUserInitials(displayDataElement.displayName) }} +
+ + +
+ + +
+
+
+
+
+
+
+
+
+ {{ getUserInitials(userDisplayName) }} +
+ + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss new file mode 100644 index 0000000000..bdb00d8808 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss @@ -0,0 +1,106 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host { + .tb-alarm-comments { + padding: 16px 24px 24px 24px; + background-color: #fafafa; + max-width: 600px; + + &-header { + background-color: #fafafa; + position: sticky; + top: -25px; + z-index: 1; + margin-bottom: 10px; + + &-title { + color: rgba(0, 0, 0, 0.76); + letter-spacing: 0.25px; + font-weight: 500; + } + + .mat-icon { + color: rgba(0, 0, 0, 0.38); + } + } + + &-user-avatar { + width: 30px; + height: 30px; + border-radius: 50%; + font-weight: 700; + color: #FFFFFF; + font-size: 13px; + } + + &-user-name { + font-size: 16px; + color: rgba(0, 0, 0, 0.76); + font-weight: 500; + letter-spacing: 0.25px; + } + + &-time { + font-size: 14px; + font-weight: 400; + color: rgba(0, 0, 0, 0.38); + letter-spacing: 0.2px + } + + &-system-text { + color: rgba(0, 0, 0, 0.38); + font-weight: 500; + letter-spacing: 0.25px; + } + + &-text { + white-space: pre-line; + word-break: break-word; + color: rgba(0, 0, 0, 0.54); + letter-spacing: 0.15px; + } + + &-action-buttons { + visibility: hidden; + .mat-icon { + color: rgba(0, 0, 0, 0.38); + } + } + + .show-buttons { + visibility: visible; + } + + .green-button { + color: #00695C; + } + + .red-button { + color: #D12730; + } + + .mat-form-field { + font-size: 16px; + letter-spacing: 0.15px; + color: rgba(0, 0, 0, 0.76); + } + + textarea { + letter-spacing: 0.15px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts new file mode 100644 index 0000000000..e117eaffc4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts @@ -0,0 +1,277 @@ +/// +/// 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. +/// + +import { Component, Input, OnInit } from '@angular/core'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { AlarmCommentService } from '@core/http/alarm-comment.service'; +import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms'; +import { DialogService } from '@core/services/dialog.service'; +import { AuthUser, User } from '@shared/models/user.model'; +import { getCurrentAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { MAX_SAFE_PAGE_SIZE, PageLink } from '@shared/models/page/page-link'; +import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; +import { map } from 'rxjs/operators'; +import { AlarmComment, AlarmCommentInfo, AlarmCommentType } from '@shared/models/alarm.models'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityType } from '@shared/models/entity-type.models'; + +interface AlarmCommentsDisplayData { + commentId?: string, + displayName?: string, + createdDateAgo?: string, + edit?: boolean, + isEdited?: boolean, + editedDateAgo?: string, + showActions?: boolean, + commentText?: string, + isSystemComment?: boolean, + avatarBgColor?: string +} + +@Component({ + selector: 'tb-alarm-comment', + templateUrl: './alarm-comment.component.html', + styleUrls: ['./alarm-comment.component.scss'] +}) +export class AlarmCommentComponent implements OnInit { + @Input() + alarmId: string; + + @Input() + commentsHeaderEnabled: boolean = true; + + authUser: AuthUser; + + alarmCommentFormGroup: FormGroup; + + alarmComments: Array; + + displayData: Array = new Array(); + + alarmCommentSortOrder: SortOrder = { + property: 'createdTime', + direction: Direction.ASC + }; + + editMode: boolean = false; + + userDisplayName$ = this.store.pipe( + select(selectUserDetails), + map((user) => this.getUserDisplayName(user)) + ); + + currentUserDisplayName: string; + currentUserAvatarColor: string; + + constructor(protected store: Store, + private translate: TranslateService, + private alarmCommentService: AlarmCommentService, + public fb: FormBuilder, + private dialogService: DialogService, + public dateAgoPipe: DateAgoPipe, + private utilsService: UtilsService) { + + this.authUser = getCurrentAuthUser(store); + + this.alarmCommentFormGroup = this.fb.group( + { + alarmCommentEdit: [''], + alarmComment: [''] + } + ); + } + + ngOnInit() { + this.loadAlarmComments(); + this.currentUserAvatarColor = this.utilsService.stringToHslColor(this.currentUserDisplayName, + 60, 40); + } + + loadAlarmComments(): void { + this.alarmCommentService.getAlarmComments(this.alarmId, new PageLink(MAX_SAFE_PAGE_SIZE, 0, null, + this.alarmCommentSortOrder)).subscribe( + (pagedData) => { + this.alarmComments = pagedData.data; + this.displayData.length = 0; + for (let alarmComment of pagedData.data) { + let displayDataElement: AlarmCommentsDisplayData = {}; + displayDataElement.createdDateAgo = this.dateAgoPipe.transform(alarmComment.createdTime); + displayDataElement.commentText = alarmComment.comment.text; + displayDataElement.isSystemComment = alarmComment.type === AlarmCommentType.SYSTEM; + if (alarmComment.type === AlarmCommentType.OTHER) { + displayDataElement.commentId = alarmComment.id.id; + displayDataElement.displayName = this.getUserDisplayName(alarmComment); + displayDataElement.edit = false; + displayDataElement.isEdited = alarmComment.comment.edited; + displayDataElement.editedDateAgo = this.dateAgoPipe.transform(alarmComment.comment.editedOn); + displayDataElement.showActions = false; + displayDataElement.isSystemComment = false; + displayDataElement.avatarBgColor = this.utilsService.stringToHslColor(displayDataElement.displayName, + 40, 60); + } + this.displayData.push(displayDataElement); + } + } + ) + } + + saveComment(): void { + const commentInputValue: string = this.getAlarmCommentFormControl().value; + if (commentInputValue) { + const comment: AlarmComment = { + alarmId: { + id: this.alarmId, + entityType: EntityType.ALARM + }, + type: AlarmCommentType.OTHER, + comment: { + text: commentInputValue + } + } + this.doSave(comment); + this.clearCommentInput(); + } + } + + saveEditedComment(commentId: string): void { + const commentEditInputValue: string = this.getAlarmCommentEditFormControl().value; + if (commentEditInputValue) { + const editedComment: AlarmComment = this.getAlarmCommentById(commentId); + editedComment.comment.text = commentEditInputValue; + this.doSave(editedComment); + this.clearCommentEditInput(); + this.editMode = false; + this.getAlarmCommentFormControl().enable({emitEvent: false}); + } + } + + private doSave(comment: AlarmComment): void { + this.alarmCommentService.saveAlarmComment(this.alarmId, comment).subscribe( + () => { + this.loadAlarmComments(); + } + ) + } + + editComment(commentId: string): void { + const commentDisplayData = this.getDataElementByCommentId(commentId); + commentDisplayData.edit = true; + this.editMode = true; + this.getAlarmCommentEditFormControl().patchValue(commentDisplayData.commentText); + this.getAlarmCommentFormControl().disable({emitEvent: false}); + } + + cancelEdit(commentId: string): void { + const commentDisplayData = this.getDataElementByCommentId(commentId); + commentDisplayData.edit = false; + this.editMode = false; + this.getAlarmCommentFormControl().enable({emitEvent: false}); + } + + deleteComment(commentId: string): void { + const alarmCommentInfo: AlarmComment = this.getAlarmCommentById(commentId); + const commentText: string = alarmCommentInfo.comment.text; + this.dialogService.confirm( + this.translate.instant('alarm-comment.delete-alarm-comment-text'), + commentText, + this.translate.instant('action.cancel'), + this.translate.instant('action.delete')).subscribe( + (result) => { + if (result) { + this.alarmCommentService.deleteAlarmComments(this.alarmId, commentId).subscribe( + () => { + this.loadAlarmComments(); + } + ) + } + } + ) + } + + onCommentMouseEnter(commentId: string, displayDataIndex: number): void { + if (!this.editMode) { + const alarmUserId = this.getAlarmCommentById(commentId).userId.id; + if (this.authUser.userId === alarmUserId) { + this.displayData[displayDataIndex].showActions = true; + } + } + } + + onCommentMouseLeave(displayDataIndex: number): void { + this.displayData[displayDataIndex].showActions = false; + } + + getUserInitials(userName: string): string { + let initials = ''; + const userNameSplit = userName.split(' '); + initials += userNameSplit[0].charAt(0).toUpperCase(); + if (userNameSplit.length > 1) { + initials += userNameSplit[userNameSplit.length - 1].charAt(0).toUpperCase(); + } + return initials; + } + + getCurrentUserBgColor(userDisplayName: string) { + return this.utilsService.stringToHslColor(userDisplayName, 40, 60); + } + + private getUserDisplayName(alarmCommentInfo: AlarmCommentInfo | User): string { + let name = ''; + if ((alarmCommentInfo.firstName && alarmCommentInfo.firstName.length > 0) || + (alarmCommentInfo.lastName && alarmCommentInfo.lastName.length > 0)) { + if (alarmCommentInfo.firstName) { + name += alarmCommentInfo.firstName; + } + if (alarmCommentInfo.lastName) { + if (name.length > 0) { + name += ' '; + } + name += alarmCommentInfo.lastName; + } + } else { + name = alarmCommentInfo.email; + } + return name; + } + + getAlarmCommentFormControl(): AbstractControl { + return this.alarmCommentFormGroup.get('alarmComment'); + } + + getAlarmCommentEditFormControl(): AbstractControl { + return this.alarmCommentFormGroup.get('alarmCommentEdit'); + } + + private clearCommentInput(): void { + this.getAlarmCommentFormControl().patchValue(''); + } + + private clearCommentEditInput(): void { + this.getAlarmCommentEditFormControl().patchValue(''); + } + + private getAlarmCommentById(id: string): AlarmComment { + return this.alarmComments.find(comment => comment.id.id === id); + } + + private getDataElementByCommentId(commentId: string): AlarmCommentsDisplayData { + return this.displayData.find(commentDisplayData => commentDisplayData.commentId === commentId); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html index 78f72f55f4..9249137443 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html @@ -28,64 +28,78 @@
-
-
+
+
- - alarm.created-time - - - + alarm.originator -
-
- - alarm.start-time - - - - alarm.end-time - - - -
-
- - alarm.ack-time - - - - alarm.clear-time - + + alarm.created-time + -
- + alarm.type - + alarm.severity - +
+
+ alarm.status
- - + + + + + + alarm.advanced-info + + + +
+ + alarm.start-time + + + + alarm.end-time + + + +
+
+ + alarm.ack-time + + + + alarm.clear-time + + + +
+ + +
+
+
+ +
+ + +
+ + +
@@ -110,15 +122,19 @@
+ + +
-
-
- {{ getUserInitials(userDisplayName) }} -
- + +
+
+ {{ getUserInitials(userDisplayName) }} +
+ - - -
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss index bdb00d8808..3cd97767a2 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - :host { .tb-alarm-comments { padding: 16px 24px 24px 24px; @@ -39,8 +38,10 @@ } &-user-avatar { - width: 30px; - height: 30px; + width: 28px; + min-width: 28px; + height: 28px; + min-height: 28px; border-radius: 50%; font-weight: 700; color: #FFFFFF; diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts index e117eaffc4..ef6e782b1e 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -66,7 +66,7 @@ export class AlarmCommentComponent implements OnInit { alarmCommentSortOrder: SortOrder = { property: 'createdTime', - direction: Direction.ASC + direction: Direction.DESC }; editMode: boolean = false; @@ -119,7 +119,7 @@ export class AlarmCommentComponent implements OnInit { displayDataElement.displayName = this.getUserDisplayName(alarmComment); displayDataElement.edit = false; displayDataElement.isEdited = alarmComment.comment.edited; - displayDataElement.editedDateAgo = this.dateAgoPipe.transform(alarmComment.comment.editedOn); + displayDataElement.editedDateAgo = this.dateAgoPipe.transform(alarmComment.comment.editedOn).toLowerCase(); displayDataElement.showActions = false; displayDataElement.isSystemComment = false; displayDataElement.avatarBgColor = this.utilsService.stringToHslColor(displayDataElement.displayName, @@ -131,6 +131,12 @@ export class AlarmCommentComponent implements OnInit { ) } + changeSortDirection() { + let currentDirection = this.alarmCommentSortOrder.direction; + this.alarmCommentSortOrder.direction = currentDirection === Direction.DESC ? Direction.ASC : Direction.DESC; + this.loadAlarmComments(); + } + saveComment(): void { const commentInputValue: string = this.getAlarmCommentFormControl().value; if (commentInputValue) { @@ -188,7 +194,7 @@ export class AlarmCommentComponent implements OnInit { const alarmCommentInfo: AlarmComment = this.getAlarmCommentById(commentId); const commentText: string = alarmCommentInfo.comment.text; this.dialogService.confirm( - this.translate.instant('alarm-comment.delete-alarm-comment-text'), + this.translate.instant('alarm-comment.delete-alarm-comment'), commentText, this.translate.instant('action.cancel'), this.translate.instant('action.delete')).subscribe( @@ -204,6 +210,18 @@ export class AlarmCommentComponent implements OnInit { ) } + getSortDirectionIcon() { + return this.alarmCommentSortOrder.direction === Direction.DESC ? 'arrow_downward' : 'arrow_upward' + } + + isDirectionAscending() { + return this.alarmCommentSortOrder.direction === Direction.ASC; + } + + isDirectionDescending() { + return this.alarmCommentSortOrder.direction === Direction.DESC; + } + onCommentMouseEnter(commentId: string, displayDataIndex: number): void { if (!this.editMode) { const alarmUserId = this.getAlarmCommentById(commentId).userId.id; diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss index e9592a0683..5af36ac0c0 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/alarm-comment-id.ts b/ui-ngx/src/app/shared/models/id/alarm-comment-id.ts index 2c6256fa11..c161e3095c 100644 --- a/ui-ngx/src/app/shared/models/id/alarm-comment-id.ts +++ b/ui-ngx/src/app/shared/models/id/alarm-comment-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts b/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts index 52fb66eb63..c0f4b5c026 100644 --- a/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -14,70 +14,42 @@ /// limitations under the License. /// -import {Pipe, PipeTransform} from '@angular/core'; +import { Inject, Pipe, PipeTransform } from '@angular/core'; import { DAY, HOUR, MINUTE, SECOND, WEEK, YEAR } from '@shared/models/time/time.models'; import { TranslateService } from '@ngx-translate/core'; -interface DateAgoInterval { - singular: string; - plural: string; - value: number; -} +const intervals = { + years: YEAR, + months: DAY * 30, + weeks: WEEK, + days: DAY, + hr: HOUR, + min: MINUTE, + sec: SECOND +}; @Pipe({ name: 'dateAgo' }) export class DateAgoPipe implements PipeTransform { - constructor(private translate: TranslateService) { + constructor(@Inject(TranslateService) private translate: TranslateService) { + } - transform(timeStamp: number): string { - if (timeStamp) { - const secondsPassed = Math.floor((+new Date() - +new Date(timeStamp)) / 1000); - if (secondsPassed < 60) - return 'recently'; - const intervalsInSeconds: Array = [ - { - singular: 'alarm-comment.year', - plural: 'alarm-comment.years', - value: YEAR / SECOND - }, - { - singular: 'alarm-comment.month', - plural: 'alarm-comment.months', - value: DAY / SECOND * 30 - }, - { - singular: 'alarm-comment.week', - plural: 'alarm-comment.weeks', - value: WEEK / SECOND - }, - { - singular: 'alarm-comment.day', - plural: 'alarm-comment.days', - value: DAY / SECOND - }, - { - singular: 'alarm-comment.hour', - plural: 'alarm-comment.hours', - value: HOUR / SECOND - }, - { - singular: 'alarm-comment.minute', - plural: 'alarm-comment.minutes', - value: MINUTE / SECOND - }, - ] - let counter: number; - for (const interval of intervalsInSeconds) { - counter = Math.floor(secondsPassed / interval.value); - if (counter > 0) - if (counter === 1) { - return counter + ' ' + this.translate.instant(interval.singular); - } else { - return counter + ' ' + this.translate.instant(interval.plural); - } + transform(value: number): string { + if (value) { + const ms = Math.floor((+new Date() - +new Date(value))); + if (ms < 29 * SECOND) { // less than 30 seconds ago will show as 'Just now' + return this.translate.instant('timewindow.just-now'); + } + let counter; + // tslint:disable-next-line:forin + for (const i in intervals) { + counter = Math.floor(ms / intervals[i]); + if (counter > 0) { + return this.translate.instant(`timewindow.${i}`, {[i]: counter}); + } } } return ''; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 033fe7dfd2..78e3ca51c9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -482,20 +482,9 @@ "add": "Add a comment...", "alarm-comment": "Alarm comment", "comments": "Comments", - "delete-alarm-comment-text": "Do you want to delete this comment?", - "refresh": "Refresh comments", - "minute": "minute", - "minutes": "minutes", - "hour": "hour", - "hours": "hours", - "day": "day", - "days": "days", - "week": "week", - "weeks": "weeks", - "month": "month", - "months": "months", - "year": "year", - "years": "years" + "delete-alarm-comment": "Do you want to delete this comment?", + "refresh": "Refresh", + "sort-direction": "Sort direction" }, "alias": { "add": "Add alias", @@ -3404,16 +3393,16 @@ "days": "Days" }, "timewindow": { + "years": "{ years, plural, 1 { year } other {# years } }", + "months": "{ months, plural, 1 { month } other {# months } }", + "weeks": "{ weeks, plural, 1 { week } other {# weeks } }", "days": "{ days, plural, 1 { day } other {# days } }", "hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# hours } }", + "hr": "{{ hr }} hr", "minutes": "{ minutes, plural, 0 { minute } 1 {1 minute } other {# minutes } }", + "min": "{{ min }} min", "seconds": "{ seconds, plural, 0 { second } 1 {1 second } other {# seconds } }", - "short": { - "days": "{ days, plural, 1 {1 day } other {# days } }", - "hours": "{ hours, plural, 1 {1 hour } other {# hours } }", - "minutes": "{{minutes}} min ", - "seconds": "{{seconds}} sec " - }, + "sec": "{{ sec }} sec", "realtime": "Realtime", "history": "History", "last-prefix": "last", @@ -3423,7 +3412,8 @@ "last": "Last", "time-period": "Time period", "hide": "Hide", - "interval": "Interval" + "interval": "Interval", + "just-now": "Just now" }, "user": { "user": "User", From 0eff914d692c58918fe202402a85df0a8fc28cd0 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 15 Feb 2023 15:44:23 +0200 Subject: [PATCH 29/60] Fix for test --- .../service/entitiy/alarm/DefaultTbAlarmServiceTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java index 3a80256a65..19efd8cff8 100644 --- a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java @@ -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,8 +82,8 @@ public class DefaultTbAlarmServiceTest { @Test public void testSave() throws ThingsboardException { - var alarm = new Alarm(); - when((Alarm) alarmSubscriptionService.createOrUpdateAlarm(alarm)).thenReturn(alarm); + var alarm = new AlarmInfo(); + when(alarmSubscriptionService.createOrUpdateAlarm(alarm)).thenReturn(alarm); service.save(alarm, new User()); verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); From 3932993b5891e940cdb5fc4cf5c601e4228ad564 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 15 Feb 2023 17:19:04 +0200 Subject: [PATCH 30/60] Alarm assignment improvements --- .../main/data/upgrade/3.4.4/schema_update.sql | 2 + .../server/dao/entity/EntityService.java | 1 + .../server/common/data/HasEmail.java | 2 +- .../server/common/data/HasLabel.java | 2 +- .../server/common/data/HasTitle.java | 2 +- .../server/common/data/alarm/Alarm.java | 4 +- .../server/common/data/alarm/AlarmInfo.java | 27 +----- .../server/dao/alarm/BaseAlarmService.java | 2 +- .../server/dao/entity/BaseEntityService.java | 84 ++++++++++--------- .../resources/sql/schema-entities-idx.sql | 2 + 10 files changed, 57 insertions(+), 71 deletions(-) diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index a36d619cb3..8b809b84c3 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -20,6 +20,8 @@ ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assign_ts BIGINT; ALTER TABLE alarm ADD COLUMN IF NOT EXISTS assignee_id UUID; +CREATE INDEX IF NOT EXISTS idx_alarm_tenant_assignee_created_time ON alarm(tenant_id, assignee_id, created_time DESC); + -- ALARM ASSIGN TO USER END CREATE TABLE IF NOT EXISTS alarm_comment ( diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java index 04b6a05685..b5bee419f5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.entity; +import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java index 7f413075f8..fd7fbf19fd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasEmail.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java index d9c3c9b98c..fe4f46c9de 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasLabel.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java index 886cef5fb1..aab1c6c346 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasTitle.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index f16920ed39..e8329bce37 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -22,6 +22,7 @@ import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasName; @@ -41,6 +42,7 @@ import java.util.List; */ @ApiModel @Data +@EqualsAndHashCode(callSuper = true) @Builder @AllArgsConstructor public class Alarm extends BaseData implements HasName, HasTenantId, HasCustomerId { @@ -71,7 +73,7 @@ public class Alarm extends BaseData implements HasName, HasTenantId, Ha private long ackTs; @ApiModelProperty(position = 14, value = "Timestamp of the alarm clearing, in milliseconds", example = "1634114528465") private long clearTs; - @ApiModelProperty(position = 15, value = "Timestamp of the alarm assigning0, in milliseconds", example = "1634115928465") + @ApiModelProperty(position = 15, value = "Timestamp of the alarm assignment, in milliseconds", example = "1634115928465") private long assignTs; @ApiModelProperty(position = 16, value = "JSON object with alarm details") private transient JsonNode details; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index ea5f2c20b0..9d3d1a6083 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java @@ -17,12 +17,14 @@ package org.thingsboard.server.common.data.alarm; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.thingsboard.server.common.data.User; import java.util.Objects; +@EqualsAndHashCode(callSuper = true) @ApiModel public class AlarmInfo extends Alarm { @@ -79,29 +81,4 @@ public class AlarmInfo extends Alarm { this.assigneeEmail = assigneeEmail; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - - AlarmInfo alarmInfo = (AlarmInfo) o; - - 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) - + (originatorLabel != null ? originatorLabel.hashCode() : 0) - + (assigneeFirstName != null ? assigneeFirstName.hashCode() : 0) - + (assigneeLastName != null ? assigneeLastName.hashCode() : 0) - + (assigneeEmail != null ? assigneeEmail.hashCode() : 0); - return result; - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index ef2526a81d..6e501fb6fb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -122,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()).orElse(null)); + alarm.setCustomerId(entityService.fetchEntityCustomerId(alarm.getTenantId(), alarm.getOriginator()).get()); if (alarm.getId() == null) { Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()); if (existing == null || existing.getStatus().isCleared()) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index dfa4cf3d7d..4d0b454b22 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -16,7 +16,9 @@ package org.thingsboard.server.dao.entity; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.HasCustomerId; @@ -37,6 +39,7 @@ import org.thingsboard.server.common.data.query.RelationsQueryFilter; import org.thingsboard.server.dao.exception.IncorrectParameterException; import java.util.Optional; +import java.util.function.Function; import static org.thingsboard.server.common.data.id.EntityId.NULL_UUID; import static org.thingsboard.server.dao.service.Validator.validateEntityDataPageLink; @@ -80,59 +83,58 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe @Override public Optional fetchEntityName(TenantId tenantId, EntityId entityId) { log.trace("Executing fetchEntityName [{}]", entityId); - EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType()); - Optional> hasIdOpt = entityDaoService.findEntity(tenantId, entityId); - if (hasIdOpt.isPresent()) { - HasId hasId = hasIdOpt.get(); - if (hasId instanceof HasName) { - HasName hasName = (HasName) hasId; - return Optional.ofNullable(hasName.getName()); - } - } - return Optional.empty(); + return fetchAndConvert(tenantId, entityId, this::getName); } @Override public Optional fetchEntityLabel(TenantId tenantId, EntityId entityId) { log.trace("Executing fetchEntityLabel [{}]", entityId); - EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType()); - Optional> 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); + return fetchAndConvert(tenantId, entityId, this::getLabel); } @Override public Optional fetchEntityCustomerId(TenantId tenantId, EntityId entityId) { log.trace("Executing fetchEntityCustomerId [{}]", entityId); + return fetchAndConvert(tenantId, entityId, this::getCustomerId); + } + + private Optional fetchAndConvert(TenantId tenantId, EntityId entityId, Function, T> converter) { EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType()); - Optional> hasIdOpt = entityDaoService.findEntity(tenantId, entityId); - if (hasIdOpt.isPresent()) { - HasId hasId = hasIdOpt.get(); - if (hasId instanceof HasCustomerId) { - HasCustomerId hasCustomerId = (HasCustomerId) hasId; - CustomerId customerId = hasCustomerId.getCustomerId(); - if (customerId == null) { - customerId = NULL_CUSTOMER_ID; - } - return Optional.of(customerId); + Optional> entityOpt = entityDaoService.findEntity(tenantId, entityId); + return entityOpt.map(converter); + } + + private String getName(HasId entity) { + return entity instanceof HasName ? ((HasName) entity).getName() : null; + } + + private String getLabel(HasId entity) { + String entityLabel = null; + 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 entityLabel; + } + + private CustomerId getCustomerId(HasId hasId) { + if (hasId instanceof HasCustomerId) { + HasCustomerId hasCustomerId = (HasCustomerId) hasId; + CustomerId customerId = hasCustomerId.getCustomerId(); + if (customerId == null) { + customerId = NULL_CUSTOMER_ID; } + return customerId; } - return Optional.of(NULL_CUSTOMER_ID); + return NULL_CUSTOMER_ID; } private static void validateEntityCountQuery(EntityCountQuery query) { diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index a5b1261d40..3671b4ed34 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -24,6 +24,8 @@ CREATE INDEX IF NOT EXISTS idx_alarm_tenant_status_created_time ON alarm(tenant_ CREATE INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_created_time ON alarm(tenant_id, type, created_time DESC); +CREATE INDEX IF NOT EXISTS idx_alarm_tenant_assignee_created_time ON alarm(tenant_id, assignee_id, created_time DESC); + CREATE INDEX IF NOT EXISTS idx_entity_alarm_created_time ON entity_alarm(tenant_id, entity_id, created_time DESC); CREATE INDEX IF NOT EXISTS idx_entity_alarm_alarm_id ON entity_alarm(alarm_id); From 74fca60cd255ca6a013d6007ddc46549b11f5f75 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 15 Feb 2023 19:17:27 +0200 Subject: [PATCH 31/60] License header --- .../org/thingsboard/server/dao/entity/BaseEntityService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index 4d0b454b22..89e0f28db2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -5,7 +5,7 @@ * 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 + * 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, From dda627b63e446704ce6651e7a655de0efdd721c7 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 16 Feb 2023 13:55:46 +0200 Subject: [PATCH 32/60] Added getting NameLabelAndCustomerDetails to EntityService --- .../server/dao/entity/EntityService.java | 5 ++-- .../data/id/NameLabelAndCustomerDetails.java | 27 +++++++++++++++++++ .../server/dao/alarm/BaseAlarmService.java | 25 ++++++++++++----- .../server/dao/entity/BaseEntityService.java | 19 +++++++++---- 4 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NameLabelAndCustomerDetails.java diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java index b5bee419f5..f2317efa05 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.dao.entity; -import org.springframework.data.util.Pair; +import org.thingsboard.server.common.data.id.NameLabelAndCustomerDetails; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -34,8 +34,9 @@ public interface EntityService { Optional fetchEntityCustomerId(TenantId tenantId, EntityId entityId); + Optional fetchNameLabelAndCustomerDetails(TenantId tenantId, EntityId entityId); + long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query); PageData findEntityDataByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query); - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NameLabelAndCustomerDetails.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NameLabelAndCustomerDetails.java new file mode 100644 index 0000000000..8d7413b438 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NameLabelAndCustomerDetails.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class NameLabelAndCustomerDetails { + private final String name; + private final String label; + private final CustomerId customerId; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 6e501fb6fb..58ce936c56 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.EntityAlarm; import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException; import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.NameLabelAndCustomerDetails; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; @@ -359,10 +360,15 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ private ListenableFuture> fetchAlarmsOriginators(TenantId tenantId, PageData alarms) { List> alarmFutures = new ArrayList<>(alarms.getData().size()); for (AlarmInfo alarmInfo : alarms.getData()) { - alarmInfo.setOriginatorName( - entityService.fetchEntityName(tenantId, alarmInfo.getOriginator()).orElse("Deleted")); - alarmInfo.setOriginatorLabel( - entityService.fetchEntityLabel(tenantId, alarmInfo.getOriginator()).orElse(null)); + Optional detailsOpt = entityService.fetchNameLabelAndCustomerDetails(tenantId, alarmInfo.getOriginator()); + if (detailsOpt.isPresent() && detailsOpt.get().getName() != null) { + NameLabelAndCustomerDetails details = detailsOpt.get(); + alarmInfo.setOriginatorName(details.getName()); + alarmInfo.setOriginatorLabel(details.getLabel()); + } else { + alarmInfo.setOriginatorName("Deleted"); + alarmInfo.setOriginatorLabel("Deleted"); + } alarmFutures.add(Futures.immediateFuture(alarmInfo)); } return Futures.transform(Futures.successfulAsList(alarmFutures), @@ -465,8 +471,15 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ String assigneeLastName = null; String assigneeEmail = null; - originatorName = entityService.fetchEntityName(tenantId, alarm.getOriginator()).orElse("Deleted"); - originatorLabel = entityService.fetchEntityLabel(tenantId, alarm.getOriginator()).orElse(null); + Optional detailsOpt = entityService.fetchNameLabelAndCustomerDetails(tenantId, alarm.getOriginator()); + if (detailsOpt.isPresent() && detailsOpt.get().getName() != null) { + NameLabelAndCustomerDetails details = detailsOpt.get(); + originatorName = details.getName(); + originatorLabel = details.getLabel(); + } else { + originatorName = "Deleted"; + originatorLabel = "Deleted"; + } if (alarm.getAssigneeId() != null) { User assignedUser = userService.findUserById(tenantId, alarm.getAssigneeId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index 89e0f28db2..9ffd818920 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -16,9 +16,7 @@ package org.thingsboard.server.dao.entity; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.HasCustomerId; @@ -26,6 +24,7 @@ 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.NameLabelAndCustomerDetails; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; @@ -98,6 +97,12 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe return fetchAndConvert(tenantId, entityId, this::getCustomerId); } + @Override + public Optional fetchNameLabelAndCustomerDetails(TenantId tenantId, EntityId entityId) { + log.trace("Executing fetchNameLabelAndCustomerDetails [{}]", entityId); + return fetchAndConvert(tenantId, entityId, this::getNameLabelAndCustomerDetails); + } + private Optional fetchAndConvert(TenantId tenantId, EntityId entityId, Function, T> converter) { EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType()); Optional> entityOpt = entityDaoService.findEntity(tenantId, entityId); @@ -125,9 +130,9 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe return entityLabel; } - private CustomerId getCustomerId(HasId hasId) { - if (hasId instanceof HasCustomerId) { - HasCustomerId hasCustomerId = (HasCustomerId) hasId; + private CustomerId getCustomerId(HasId entity) { + if (entity instanceof HasCustomerId) { + HasCustomerId hasCustomerId = (HasCustomerId) entity; CustomerId customerId = hasCustomerId.getCustomerId(); if (customerId == null) { customerId = NULL_CUSTOMER_ID; @@ -137,6 +142,10 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe return NULL_CUSTOMER_ID; } + private NameLabelAndCustomerDetails getNameLabelAndCustomerDetails(HasId entity) { + return new NameLabelAndCustomerDetails(getName(entity), getLabel(entity), getCustomerId(entity)); + } + private static void validateEntityCountQuery(EntityCountQuery query) { if (query == null) { throw new IncorrectParameterException("Query must be specified."); From c392df2b8f5896505ca123ac6781e27263c62eec Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 16 Feb 2023 14:10:50 +0200 Subject: [PATCH 33/60] Alarm Query to fetch onloy assigned alarms --- .../controller/EntityQueryController.java | 35 ++++------- .../server/common/data/alarm/AlarmInfo.java | 2 + .../server/common/data/query/AlarmData.java | 4 ++ .../common/data/query/AlarmDataPageLink.java | 11 ++-- .../server/dao/alarm/BaseAlarmService.java | 6 +- .../query/DefaultAlarmQueryRepository.java | 7 ++- .../dao/service/BaseAlarmServiceTest.java | 63 ++++++++++++++----- 7 files changed, 81 insertions(+), 47 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java index 716b7ef60f..1d429e9157 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java @@ -29,6 +29,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.common.data.exception.ThingsboardException; 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; @@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.query.EntityQueryService; +import org.thingsboard.server.service.security.permission.Operation; import static org.thingsboard.server.controller.ControllerConstants.ALARM_DATA_QUERY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_COUNT_QUERY_DESCRIPTION; @@ -61,11 +63,7 @@ public class EntityQueryController extends BaseController { @ApiParam(value = "A JSON value representing the entity count query. See API call notes above for more details.") @RequestBody EntityCountQuery query) throws ThingsboardException { checkNotNull(query); - try { - return this.entityQueryService.countEntitiesByQuery(getCurrentUser(), query); - } catch (Exception e) { - throw handleException(e); - } + return this.entityQueryService.countEntitiesByQuery(getCurrentUser(), query); } @ApiOperation(value = "Find Entity Data by Query", notes = ENTITY_DATA_QUERY_DESCRIPTION) @@ -76,11 +74,7 @@ public class EntityQueryController extends BaseController { @ApiParam(value = "A JSON value representing the entity data query. See API call notes above for more details.") @RequestBody EntityDataQuery query) throws ThingsboardException { checkNotNull(query); - try { - return this.entityQueryService.findEntityDataByQuery(getCurrentUser(), query); - } catch (Exception e) { - throw handleException(e); - } + return this.entityQueryService.findEntityDataByQuery(getCurrentUser(), query); } @ApiOperation(value = "Find Alarms by Query", notes = ALARM_DATA_QUERY_DESCRIPTION) @@ -91,11 +85,12 @@ public class EntityQueryController extends BaseController { @ApiParam(value = "A JSON value representing the alarm data query. See API call notes above for more details.") @RequestBody AlarmDataQuery query) throws ThingsboardException { checkNotNull(query); - try { - return this.entityQueryService.findAlarmDataByQuery(getCurrentUser(), query); - } catch (Exception e) { - throw handleException(e); + checkNotNull(query.getPageLink()); + UserId assigneeId = query.getPageLink().getAssigneeId(); + if (assigneeId != null) { + checkUserId(assigneeId, Operation.READ); } + return this.entityQueryService.findAlarmDataByQuery(getCurrentUser(), query); } @ApiOperation(value = "Find Entity Keys by Query", @@ -112,15 +107,11 @@ public class EntityQueryController extends BaseController { @RequestParam("attributes") boolean isAttributes) throws ThingsboardException { TenantId tenantId = getTenantId(); checkNotNull(query); - try { - EntityDataPageLink pageLink = query.getPageLink(); - if (pageLink.getPageSize() > MAX_PAGE_SIZE) { - pageLink.setPageSize(MAX_PAGE_SIZE); - } - return entityQueryService.getKeysByQuery(getCurrentUser(), tenantId, query, isTimeseries, isAttributes); - } catch (Exception e) { - throw handleException(e); + EntityDataPageLink pageLink = query.getPageLink(); + if (pageLink.getPageSize() > MAX_PAGE_SIZE) { + pageLink.setPageSize(MAX_PAGE_SIZE); } + return entityQueryService.getKeysByQuery(getCurrentUser(), tenantId, query, isTimeseries, isAttributes); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index 9d3d1a6083..276c2a85b4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java @@ -20,11 +20,13 @@ import io.swagger.annotations.ApiModelProperty; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import lombok.ToString; import org.thingsboard.server.common.data.User; import java.util.Objects; @EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) @ApiModel public class AlarmInfo extends Alarm { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java index 3c691bdfb5..81c5a1d3b8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.query; +import lombok.EqualsAndHashCode; import lombok.Getter; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -24,8 +25,11 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +@EqualsAndHashCode(callSuper = true) public class AlarmData extends AlarmInfo { + private static final long serialVersionUID = -7042457913823369638L; + @Getter private final EntityId entityId; @Getter diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataPageLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataPageLink.java index e46deddfd7..9c0253272c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataPageLink.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataPageLink.java @@ -19,11 +19,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Getter; import lombok.ToString; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.id.UserId; import java.util.List; @@ -41,6 +40,7 @@ public class AlarmDataPageLink extends EntityDataPageLink { private List statusList; private List severityList; private boolean searchPropagatedAlarms; + private UserId assigneeId; public AlarmDataPageLink() { super(); @@ -49,7 +49,8 @@ public class AlarmDataPageLink extends EntityDataPageLink { public AlarmDataPageLink(int pageSize, int page, String textSearch, EntityDataSortOrder sortOrder, boolean dynamic, boolean searchPropagatedAlarms, long startTs, long endTs, long timeWindow, - List typeList, List statusList, List severityList) { + List typeList, List statusList, List severityList, + UserId assigneeId) { super(pageSize, page, textSearch, sortOrder, dynamic); this.searchPropagatedAlarms = searchPropagatedAlarms; this.startTs = startTs; @@ -58,6 +59,7 @@ public class AlarmDataPageLink extends EntityDataPageLink { this.typeList = typeList; this.statusList = statusList; this.severityList = severityList; + this.assigneeId = assigneeId; } @JsonIgnore @@ -65,7 +67,8 @@ public class AlarmDataPageLink extends EntityDataPageLink { return new AlarmDataPageLink(this.getPageSize(), this.getPage() + 1, this.getTextSearch(), this.getSortOrder(), this.isDynamic(), this.searchPropagatedAlarms, this.startTs, this.endTs, this.timeWindow, - this.typeList, this.statusList, this.severityList + this.typeList, this.statusList, this.severityList, + this.assigneeId ); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 6e501fb6fb..9279abbdb2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -122,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()) { @@ -362,7 +362,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarmInfo.setOriginatorName( entityService.fetchEntityName(tenantId, alarmInfo.getOriginator()).orElse("Deleted")); alarmInfo.setOriginatorLabel( - entityService.fetchEntityLabel(tenantId, alarmInfo.getOriginator()).orElse(null)); + entityService.fetchEntityLabel(tenantId, alarmInfo.getOriginator()).orElse(alarmInfo.getOriginatorName())); alarmFutures.add(Futures.immediateFuture(alarmInfo)); } return Futures.transform(Futures.successfulAsList(alarmFutures), @@ -466,7 +466,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ String assigneeEmail = null; originatorName = entityService.fetchEntityName(tenantId, alarm.getOriginator()).orElse("Deleted"); - originatorLabel = entityService.fetchEntityLabel(tenantId, alarm.getOriginator()).orElse(null); + originatorLabel = entityService.fetchEntityLabel(tenantId, alarm.getOriginator()).orElse(originatorName); if (alarm.getAssigneeId() != null) { User assignedUser = userService.findUserById(tenantId, alarm.getAssigneeId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index 5dd0e00eb0..91c0694422 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -151,7 +151,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { 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 LEFT_JOIN_TB_USERS = "left join tb_user tbu on a.assignee_id = tbu.id "; + private static final String LEFT_JOIN_TB_USERS = "left join tb_user tbu on tbu.id = a.assignee_id "; protected final NamedParameterJdbcTemplate jdbcTemplate; private final TransactionTemplate transactionTemplate; @@ -281,6 +281,11 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { } } + if (pageLink.getAssigneeId() != null){ + ctx.addUuidParameter("assigneeId", pageLink.getAssigneeId().getId()); + wherePart.append(" a.assignee_id = :assigneeId"); + } + String mainQuery = String.format("%s%s", selectPart, fromPart); if (textSearchQuery.isEmpty()) { mainQuery = String.format("%s%s%s", mainQuery, joinPart, wherePart); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java index 6f03ae1192..f451b6232b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.query.AlarmData; @@ -238,7 +239,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = new Alarm(result.getAlarmInfo()); + AlarmInfo created = result.getAlarmInfo(); User tenantUser = new User(); tenantUser.setTenantId(tenantId); @@ -262,6 +263,32 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(created, alarms.getData().get(0)); + + AlarmDataPageLink pageLink = new AlarmDataPageLink(); + pageLink.setPage(0); + pageLink.setPageSize(10); + pageLink.setAssigneeId(tenantUser.getId()); + + PageData assignedAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(created.getOriginator())); + Assert.assertNotNull(assignedAlarms.getData()); + Assert.assertEquals(1, assignedAlarms.getData().size()); + Assert.assertEquals(created, new AlarmInfo(assignedAlarms.getData().get(0))); + + User tenantUser2 = new User(); + tenantUser2.setTenantId(tenantId); + tenantUser2.setAuthority(Authority.TENANT_ADMIN); + tenantUser2.setEmail(2 + TEST_TENANT_EMAIL); + tenantUser2.setFirstName(TEST_TENANT_FIRST_NAME); + tenantUser2.setLastName(TEST_TENANT_LAST_NAME); + tenantUser2 = userService.saveUser(tenantUser2); + + Assert.assertNotNull(tenantUser2); + pageLink.setAssigneeId(tenantUser2.getId()); + + PageData assignedToNonExistingUserAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(created.getOriginator())); + Assert.assertNotNull(assignedToNonExistingUserAlarms.getData()); + Assert.assertTrue(assignedToNonExistingUserAlarms.getData().isEmpty()); + } @Test @@ -301,7 +328,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) .startTs(ts).build(); result = alarmService.createOrUpdateAlarm(deviceAlarm); - deviceAlarm = new Alarm(result.getAlarmInfo()); + deviceAlarm = result.getAlarmInfo(); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -319,7 +346,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customerDevice.getId())); Assert.assertEquals(1, customerAlarms.getData().size()); - Assert.assertEquals(deviceAlarm, customerAlarms.getData().get(0)); + Assert.assertEquals(deviceAlarm, new AlarmInfo(customerAlarms.getData().get(0))); PageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(tenantDevice.getId()) @@ -393,7 +420,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { //TEST that propagated alarms are visible on the asset level. PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customerAsset.getId())); Assert.assertEquals(1, customerAlarms.getData().size()); - Assert.assertEquals(customerAlarm, customerAlarms.getData().get(0)); + Assert.assertEquals(customerAlarm, new Alarm(customerAlarms.getData().get(0))); } @Test @@ -444,12 +471,12 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { //TEST that propagated alarms are visible on the asset level. PageData tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(tenantId)); Assert.assertEquals(1, tenantAlarms.getData().size()); - Assert.assertEquals(tenantAlarm, tenantAlarms.getData().get(0)); + Assert.assertEquals(tenantAlarm, new Alarm(tenantAlarms.getData().get(0))); //TEST that propagated alarms are visible on the asset level. PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customer.getId())); Assert.assertEquals(1, customerAlarms.getData().size()); - Assert.assertEquals(customerAlarm, customerAlarms.getData().get(0)); + Assert.assertEquals(customerAlarm, new Alarm(customerAlarms.getData().get(0))); } private AlarmDataQuery toQuery(AlarmDataPageLink pageLink) { @@ -537,7 +564,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { .startTs(ts).build(); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); - Alarm created = new Alarm(result.getAlarmInfo()); + AlarmInfo created = result.getAlarmInfo(); AlarmDataPageLink pageLink = new AlarmDataPageLink(); pageLink.setPage(0); @@ -554,7 +581,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); - Assert.assertEquals(created, new Alarm(alarms.getData().get(0))); + Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0))); pageLink.setPage(0); pageLink.setPageSize(10); @@ -569,18 +596,18 @@ 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, new Alarm(alarms.getData().get(0))); + Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0))); pageLink.setSearchPropagatedAlarms(true); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId)); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); - Assert.assertEquals(created, new Alarm(alarms.getData().get(0))); + Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0))); // Check child relation created.setPropagate(true); result = alarmService.createOrUpdateAlarm(created); - created = new Alarm(result.getAlarmInfo()); + created = result.getAlarmInfo(); // Check child relation pageLink.setPage(0); @@ -596,7 +623,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, new Alarm(alarms.getData().get(0))); + Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0))); // Check parent relation pageLink.setPage(0); @@ -612,10 +639,11 @@ 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, new Alarm(alarms.getData().get(0))); + Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0))); PageData alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) + .fetchOriginator(true) .status(AlarmStatus.ACTIVE_UNACK).pageLink( new TimePageLink(10, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) @@ -626,6 +654,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(parentId) + .fetchOriginator(true) .status(AlarmStatus.ACTIVE_UNACK).pageLink( new TimePageLink(10, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) @@ -636,6 +665,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(parentId2) + .fetchOriginator(true) .status(AlarmStatus.ACTIVE_UNACK).pageLink( new TimePageLink(10, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) @@ -657,10 +687,9 @@ 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, new Alarm(alarms.getData().get(0))); + Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0))); - alarmService.ackAlarm(tenantId, created.getId(), System.currentTimeMillis()).get(); - created = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get(); + created = alarmService.ackAlarm(tenantId, created.getId(), System.currentTimeMillis()).get().getAlarmInfo(); pageLink.setPage(0); pageLink.setPageSize(10); @@ -675,7 +704,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, new Alarm(alarms.getData().get(0))); + Assert.assertEquals(created, new AlarmInfo(alarms.getData().get(0))); } @Test From 994971a5cd72dc5de9945bc00e9c4ed93c78a4c4 Mon Sep 17 00:00:00 2001 From: rusikv Date: Thu, 16 Feb 2023 14:51:24 +0200 Subject: [PATCH 34/60] UI alarm assignment implementation --- ui-ngx/src/app/core/http/alarm.service.ts | 8 + ui-ngx/src/app/core/services/utils.service.ts | 9 +- .../alarm/alarm-assignee-panel.component.html | 51 ++++ .../alarm/alarm-assignee-panel.component.scss | 101 ++++++++ .../alarm/alarm-assignee-panel.component.ts | 227 ++++++++++++++++++ .../alarm/alarm-details-dialog.component.html | 99 +++++--- .../alarm/alarm-details-dialog.component.scss | 61 +++++ .../alarm/alarm-details-dialog.component.ts | 53 +++- .../components/alarm/alarm-table-config.ts | 145 ++++++++++- .../alarm/alarm-table.component.scss | 27 +++ .../components/alarm/alarm-table.component.ts | 16 +- .../home/components/home-components.module.ts | 3 + .../lib/alarms-table-widget.component.html | 24 +- .../lib/alarms-table-widget.component.scss | 36 ++- .../lib/alarms-table-widget.component.ts | 92 ++++++- .../assets/locale/locale.constant-en_US.json | 13 +- 16 files changed, 910 insertions(+), 55 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss diff --git a/ui-ngx/src/app/core/http/alarm.service.ts b/ui-ngx/src/app/core/http/alarm.service.ts index 802045b684..076f590199 100644 --- a/ui-ngx/src/app/core/http/alarm.service.ts +++ b/ui-ngx/src/app/core/http/alarm.service.ts @@ -60,6 +60,14 @@ export class AlarmService { return this.http.post(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config)); } + public assignAlarm(alarmId: string, assigneeId: string, config?: RequestConfig): Observable { + return this.http.post(`/api/alarm/${alarmId}/assign/${assigneeId}`, null, defaultHttpOptionsFromConfig(config)); + } + + public unassignAlarm(alarmId: string, config?: RequestConfig): Observable { + return this.http.delete(`/api/alarm/${alarmId}/assign`, defaultHttpOptionsFromConfig(config)); + } + public deleteAlarm(alarmId: string, config?: RequestConfig): Observable { return this.http.delete(`/api/alarm/${alarmId}`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index ce6cf32dbd..ea4c85eb64 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -25,7 +25,7 @@ import { createLabelFromDatasource, deepClone, deleteNullProperties, - guid, + guid, hashCode, isDefined, isDefinedAndNotNull, isString, @@ -405,6 +405,13 @@ export class UtilsService { }); } + public stringToHslColor(str: string, saturationPercentage: number, lightnessPercentage: number): string { + if (str && str.length) { + let hue = hashCode(str) % 360; + return `hsl(${hue}, ${saturationPercentage}%, ${lightnessPercentage}%)`; + } + } + public currentPerfTime(): number { return this.window.performance && this.window.performance.now ? this.window.performance.now() : Date.now(); diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html new file mode 100644 index 0000000000..262a45e435 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html @@ -0,0 +1,51 @@ + + + + search + + + account_circle + alarm.unassigned + + + + +
+ + +
+
+ + + {{ translate.get('user.no-users-matching', {entity: searchText}) | async }} + + +
+
+ diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss new file mode 100644 index 0000000000..462df2e0d2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss @@ -0,0 +1,101 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + width: 100%; + overflow: auto; + background: #fff; + box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3), 0px 2px 6px 2px rgba(0, 0, 0, 0.15); + border-radius: 4px; +} + +::ng-deep { + mat-form-field.container { + padding-top: 8px; + height: 340px; + font-size: 14px; + + background-color: #fff; + .mat-form-field-wrapper { + margin-right: 8px; + margin-left: 8px; + padding: 0; + } + } + + .mat-form-field-appearance-outline .mat-form-field-outline { + color: rgba(0, 0, 0, 0.12) !important; + } + + .mat-form-field-appearance-outline.mat-focused .mat-form-field-outline-thick { + color: rgba(0, 0, 0, 0.12) !important; + } + + .tb-assignee-autocomplete { + + &.tb-assignee-autocomplete.mat-autocomplete-visible { + position: relative; + left: -8px; + margin-top: 8px; + box-shadow: none !important; + } + .assigned { + background-color: rgba(0, 0, 0, 0.06); + } + + .mat-option { + font-size: 14px; + border: none; + height: 52px !important; + .unassigned-icon { + color: rgba(0, 0, 0, 0.38); + font-size: 28px; + width: 28px; + height: 28px; + margin-right: 8px; + } + .user-avatar { + display: inline-flex; + justify-content: center; + align-items: center; + align-self: center; + margin-right: 8px; + border-radius: 50%; + background-color: #5cb445; + width: 28px; + height: 28px; + color: white; + font-size: 13px; + font-weight: 700 + } + .user-email { + color: rgba(0, 0, 0, 0.76); + overflow: hidden; + text-overflow: ellipsis; + max-width: 185px + } + .user-name { + color: rgba(0, 0, 0, 0.38); + font-size: 13px; + } + .mat-option-text { + display: flex; + justify-content: start; + align-items: center; + line-height: normal; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts new file mode 100644 index 0000000000..910a6f57c0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts @@ -0,0 +1,227 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, + ElementRef, + Inject, + InjectionToken, OnDestroy, + OnInit, + ViewChild +} from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { Observable, of, Subject } from 'rxjs'; +import { + catchError, + debounceTime, + distinctUntilChanged, + map, + share, + switchMap, + takeUntil, +} from 'rxjs/operators'; +import { User } from '@shared/models/user.model'; +import { TranslateService } from '@ngx-translate/core'; +import { UserService } from '@core/http/user.service'; +import { PageLink } from '@shared/models/page/page-link'; +import { Direction } from '@shared/models/page/sort-order'; +import { emptyPageData } from '@shared/models/page/page-data'; +import { AlarmService } from '@core/http/alarm.service'; +import { OverlayRef } from '@angular/cdk/overlay'; +import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; +import { UtilsService } from '@core/services/utils.service'; + +export const ALARM_ASSIGNEE_PANEL_DATA = new InjectionToken('AlarmAssigneePanelData'); + +export interface AlarmAssigneePanelData { + alarmId: string; + assigneeId: string; +} + +@Component({ + selector: 'tb-alarm-assignee-panel', + templateUrl: './alarm-assignee-panel.component.html', + styleUrls: ['./alarm-assignee-panel.component.scss'] +}) +export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDestroy { + + private dirty = false; + + alarmId: string; + + assigneeId?: string; + + selectUserFormGroup: FormGroup; + + @ViewChild('userInput', {static: true}) userInput: ElementRef; + + filteredUsers: Observable>; + + searchText = ''; + + private destroy$ = new Subject(); + + constructor(@Inject(ALARM_ASSIGNEE_PANEL_DATA) public data: AlarmAssigneePanelData, + public overlayRef: OverlayRef, + public translate: TranslateService, + private userService: UserService, + private alarmService: AlarmService, + private fb: FormBuilder, + private utilsService: UtilsService) { + this.alarmId = data.alarmId; + this.assigneeId = data.assigneeId; + this.selectUserFormGroup = this.fb.group({ + user: [null] + }); + } + + ngOnInit() { + this.filteredUsers = this.selectUserFormGroup.get('user').valueChanges + .pipe( + debounceTime(150), + map(value => { + return value ? (typeof value === 'string' ? value : '') : '' + }), + distinctUntilChanged(), + switchMap(name => this.fetchUsers(name)), + share(), + takeUntil(this.destroy$) + ); + } + + ngAfterViewInit() { + setTimeout(() => { + this.userInput.nativeElement.focus(); + }, 0) + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + displayUserFn(user?: User): string | undefined { + return user ? user.email : undefined; + } + + selected(event: MatAutocompleteSelectedEvent): void { + this.selectUserFormGroup.get('user').patchValue(''); + const user: User = event.option.value; + if (user) { + this.assign(user); + } else { + this.unassign(); + } + } + + assign(user: User): void { + this.alarmService.assignAlarm(this.alarmId, user.id.id).subscribe( + () => this.overlayRef.dispose()); + } + + unassign(): void { + this.alarmService.unassignAlarm(this.alarmId).subscribe( + () => this.overlayRef.dispose()); + } + + fetchUsers(searchText?: string): Observable> { + this.searchText = searchText; + const pageLink = new PageLink(50, 0, searchText, { + property: 'email', + direction: Direction.ASC + }); + return this.userService.getUsers(pageLink, {ignoreLoading: true}) + .pipe( + catchError(() => of(emptyPageData())), + map(pageData => { + return pageData.data; + }) + ); + } + + onFocus(): void { + if (!this.dirty) { + this.selectUserFormGroup.get('user').updateValueAndValidity({onlySelf: true}); + this.dirty = true; + } + } + + clear() { + this.selectUserFormGroup.get('user').patchValue('', {emitEvent: true}); + setTimeout(() => { + this.userInput.nativeElement.blur(); + this.userInput.nativeElement.focus(); + }, 0); + } + + getUserDisplayName(entity: User) { + let displayName = ''; + if ((entity.firstName && entity.firstName.length > 0) || + (entity.lastName && entity.lastName.length > 0)) { + if (entity.firstName) { + displayName += entity.firstName; + } + if (entity.lastName) { + if (displayName.length > 0) { + displayName += ' '; + } + displayName += entity.lastName; + } + } else { + displayName = entity.email; + } + return displayName; + } + + getUserInitials(entity: User): string { + let initials = ''; + if (entity.firstName && entity.firstName.length || + entity.lastName && entity.lastName.length) { + if (entity.firstName) { + initials += entity.firstName.charAt(0); + } + if (entity.lastName) { + initials += entity.lastName.charAt(0); + } + } else { + initials += entity.email.charAt(0); + } + return initials.toUpperCase(); + } + + getFullName(entity: User): string { + let fullName = ''; + if ((entity.firstName && entity.firstName.length > 0) || + (entity.lastName && entity.lastName.length > 0)) { + if (entity.firstName) { + fullName += entity.firstName; + } + if (entity.lastName) { + if (fullName.length > 0) { + fullName += ' '; + } + fullName += entity.lastName; + } + } + return fullName; + } + + getAvatarBgColor(entity: User) { + return this.utilsService.stringToHslColor(this.getUserDisplayName(entity), 40, 60); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html index 3bdb10db57..eb67fe32a6 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html @@ -28,63 +28,84 @@
-
-
+
+
- - alarm.created-time - - - + alarm.originator -
-
- - alarm.start-time - - - - alarm.end-time - - - -
-
- - alarm.ack-time - - - - alarm.clear-time - + + alarm.created-time + -
- + alarm.type - + alarm.severity - +
+
+ alarm.status + + alarm.assignee + + {{ assigneeInitials }} + + +
- - + + + + + + alarm.advanced-info + + + +
+ + alarm.start-time + + + + alarm.end-time + + + +
+
+ + alarm.ack-time + + + + alarm.clear-time + + + +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss new file mode 100644 index 0000000000..76148a1dd5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host ::ng-deep { + .assignee-field { + .mat-form-field-label-wrapper { + overflow: visible; + left: -36px; + } + .user-avatar { + min-width: 28px; + min-height: 28px; + margin-right: 8px; + border-radius: 50%; + font-weight: 700; + color: #fff; + font-size: 13px; + line-height: normal; + } + } + .mat-expansion-panel { + &.tb-alarm-details { + box-shadow: none; + margin-bottom: 2px; + &.mat-expanded { + margin-bottom: 24px; + } + .mat-expansion-panel-header { + padding: 0; + height: 24px; + &:hover { + background: none !important; + } + .mat-expansion-indicator { + padding: 2px; + } + } + .mat-expansion-panel-header-description { + align-items: center; + } + .mat-expansion-panel-body { + padding: 0; + } + } + .mat-expansion-panel-content { + font: inherit; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts index 35db88231d..2e97ce7405 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts @@ -33,6 +33,7 @@ import { AlarmService } from '@core/http/alarm.service'; import { tap } from 'rxjs/operators'; import { DatePipe } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; +import { UtilsService } from '@core/services/utils.service'; export interface AlarmDetailsDialogData { alarmId?: string; @@ -45,7 +46,7 @@ export interface AlarmDetailsDialogData { @Component({ selector: 'tb-alarm-details-dialog', templateUrl: './alarm-details-dialog.component.html', - styleUrls: [] + styleUrls: ['./alarm-details-dialog.component.scss'] }) export class AlarmDetailsDialogComponent extends DialogComponent implements OnInit { @@ -66,6 +67,8 @@ export class AlarmDetailsDialogComponent extends DialogComponent, protected router: Router, private datePipe: DatePipe, @@ -73,7 +76,8 @@ export class AlarmDetailsDialogComponent extends DialogComponent, - public fb: UntypedFormBuilder) { + public fb: UntypedFormBuilder, + private utilsService: UtilsService) { super(store, router, dialogRef); this.allowAcknowledgment = data.allowAcknowledgment; @@ -95,6 +99,7 @@ export class AlarmDetailsDialogComponent extends DialogComponent 0) || + (entity.assigneeLastName && entity.assigneeLastName.length > 0)) { + if (entity.assigneeFirstName) { + displayName += entity.assigneeFirstName; + } + if (entity.assigneeLastName) { + if (displayName.length > 0) { + displayName += ' '; + } + displayName += entity.assigneeLastName; + } + } else { + displayName = entity.assigneeEmail; + } + return displayName; + } + + getUserInitials(entity: AlarmInfo): string { + let initials = ''; + if (entity.assigneeFirstName && entity.assigneeFirstName.length || + entity.assigneeLastName && entity.assigneeLastName.length) { + if (entity.assigneeFirstName) { + initials += entity.assigneeFirstName.charAt(0); + } + if (entity.assigneeLastName) { + initials += entity.assigneeLastName.charAt(0); + } + } else { + initials += entity.assigneeEmail.charAt(0); + } + return initials.toUpperCase(); + } + } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index 669be64636..f29dae1926 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -15,6 +15,7 @@ /// import { + CellActionDescriptorType, DateEntityTableColumn, EntityTableColumn, EntityTableConfig @@ -48,6 +49,15 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { Authority } from '@shared/models/authority.enum'; +import { ChangeDetectorRef, Injector, StaticProvider, ViewContainerRef } from '@angular/core'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { + ALARM_ASSIGNEE_PANEL_DATA, AlarmAssigneePanelComponent, + AlarmAssigneePanelData +} from '@home/components/alarm/alarm-assignee-panel.component'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { isDefinedAndNotNull } from '@core/utils'; +import { UtilsService } from '@core/services/utils.service'; export class AlarmTableConfig extends EntityTableConfig { @@ -62,7 +72,11 @@ export class AlarmTableConfig extends EntityTableConfig private dialog: MatDialog, public entityId: EntityId = null, private defaultSearchStatus: AlarmSearchStatus = AlarmSearchStatus.ANY, - private store: Store) { + private store: Store, + private viewContainerRef: ViewContainerRef, + private overlay: Overlay, + private cd: ChangeDetectorRef, + private utilsService: UtilsService) { super(); this.loadDataOnInit = false; this.tableTitle = ''; @@ -97,10 +111,35 @@ export class AlarmTableConfig extends EntityTableConfig this.columns.push( new EntityTableColumn('severity', 'alarm.severity', '25%', (entity) => this.translate.instant(alarmSeverityTranslations.get(entity.severity)), - entity => ({ - fontWeight: 'bold', - color: alarmSeverityColors.get(entity.severity) - }))); + entity => ({ + fontWeight: 'bold', + color: alarmSeverityColors.get(entity.severity) + }))); + this.columns.push( + new EntityTableColumn('assigneeEmail', 'alarm.assignee', '200px', + (entity) => { + return this.getAssigneeTemplate(entity) + }, + (entity) => { + return { + display: 'flex', + justifyContent: 'start', + alignItems: 'center', + height: 'inherit' + } + }, + false, + () => ({}), + (entity) => undefined, + false, + { + icon: 'keyboard_arrow_down', + type: CellActionDescriptorType.DEFAULT, + isEnabled: (entity) => true, + name: this.translate.instant('alarm.assign'), + onAction: ($event, entity) => this.openAlarmAssigneePanel($event, entity) + }) + ) this.columns.push( new EntityTableColumn('status', 'alarm.status', '25%', (entity) => this.translate.instant(alarmStatusTranslations.get(entity.status)))); @@ -142,4 +181,100 @@ export class AlarmTableConfig extends EntityTableConfig } ); } + + getAssigneeTemplate(entity: AlarmInfo): string { + return ` + + ${isDefinedAndNotNull(entity.assigneeId) ? + ` + + ${this.getUserInitials(entity)} + + ${this.getUserDisplayName(entity)} + ` + : + `account_circle + ${this.translate.instant('alarm.unassigned')}` + } + ` + } + + getUserDisplayName(entity: AlarmInfo) { + let displayName = ''; + if ((entity.assigneeFirstName && entity.assigneeFirstName.length > 0) || + (entity.assigneeLastName && entity.assigneeLastName.length > 0)) { + if (entity.assigneeFirstName) { + displayName += entity.assigneeFirstName; + } + if (entity.assigneeLastName) { + if (displayName.length > 0) { + displayName += ' '; + } + displayName += entity.assigneeLastName; + } + } else { + displayName = entity.assigneeEmail; + } + return displayName; + } + + getUserInitials(entity: AlarmInfo): string { + let initials = ''; + if (entity.assigneeFirstName && entity.assigneeFirstName.length || + entity.assigneeLastName && entity.assigneeLastName.length) { + if (entity.assigneeFirstName) { + initials += entity.assigneeFirstName.charAt(0); + } + if (entity.assigneeLastName) { + initials += entity.assigneeLastName.charAt(0); + } + } else { + initials += entity.assigneeEmail.charAt(0); + } + return initials.toUpperCase(); + } + + getAvatarBgColor(entity: AlarmInfo) { + return this.utilsService.stringToHslColor(this.getUserDisplayName(entity), 40, 60); + } + + openAlarmAssigneePanel($event: Event, entity: AlarmInfo) { + if ($event) { + $event.stopPropagation(); + } + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'center', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + config.minWidth = '260px'; + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + const providers: StaticProvider[] = [ + { + provide: ALARM_ASSIGNEE_PANEL_DATA, + useValue: { + alarmId: entity.id.id, + assigneeId: entity.assigneeId?.id + } as AlarmAssigneePanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); + overlayRef.attach(new ComponentPortal(AlarmAssigneePanelComponent, + this.viewContainerRef, injector)).onDestroy(() => this.updateData()); + } + } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss index b80526aee8..b6dcfdecea 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss @@ -17,6 +17,33 @@ tb-entities-table { .mat-drawer-container { background-color: white; + .assignee-cell { + display: flex; + justify-content: flex-start; + align-items: center; + .assigned-container { + .user-avatar { + display: inline-flex; + justify-content: center; + align-items: center; + margin-right: 8px; + border-radius: 50%; + width: 28px; + height: 28px; + color: white; + font-size: 13px; + font-weight: 700; + } + } + .material-icons.unassigned-icon { + width: 28px; + height: 28px; + font-size: 28px; + margin-right: 8px; + color: rgba(0, 0, 0, 0.38); + overflow: visible; + } + } } } } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts index 0d53eaaf42..5885c38db0 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; import { MatDialog } from '@angular/material/dialog'; @@ -26,6 +26,8 @@ import { AlarmSearchStatus } from '@shared/models/alarm.models'; import { AlarmService } from '@app/core/http/alarm.service'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; +import { Overlay } from '@angular/cdk/overlay'; +import { UtilsService } from '@core/services/utils.service'; @Component({ selector: 'tb-alarm-table', @@ -71,7 +73,11 @@ export class AlarmTableComponent implements OnInit { private translate: TranslateService, private datePipe: DatePipe, private dialog: MatDialog, - private store: Store) { + private store: Store, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private cd: ChangeDetectorRef, + private utilsService: UtilsService) { } ngOnInit() { @@ -84,7 +90,11 @@ export class AlarmTableComponent implements OnInit { this.dialog, this.entityIdValue, AlarmSearchStatus.ANY, - this.store + this.store, + this.viewContainerRef, + this.overlay, + this.cd, + this.utilsService ); } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 99d30c70cd..917f608f94 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -175,6 +175,7 @@ import { AssetProfileDialogComponent } from '@home/components/profile/asset-prof import { AssetProfileAutocompleteComponent } from '@home/components/profile/asset-profile-autocomplete.component'; import { MODULES_MAP } from '@shared/models/constants'; import { modulesMap } from '@modules/common/modules-map'; +import { AlarmAssigneePanelComponent } from '@home/components/alarm/alarm-assignee-panel.component'; @NgModule({ declarations: @@ -197,6 +198,7 @@ import { modulesMap } from '@modules/common/modules-map'; RelationFiltersComponent, AlarmTableHeaderComponent, AlarmTableComponent, + AlarmAssigneePanelComponent, AttributeTableComponent, AddAttributeDialogComponent, EditAttributeValuePanelComponent, @@ -344,6 +346,7 @@ import { modulesMap } from '@modules/common/modules-map'; RelationTableComponent, RelationFiltersComponent, AlarmTableComponent, + AlarmAssigneePanelComponent, AttributeTableComponent, AliasesEntitySelectComponent, AliasesEntityAutocompleteComponent, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html index 9e431c1ccd..def95c93f9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html @@ -82,8 +82,30 @@ {{ column.title }} + + + + + + {{ getUserInitials(alarm) }} + + {{ getUserDisplayName(alarm) }} + + + account_circle + alarm.unassigned + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss index 1ee9b509f2..aecef669a4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host { +:host::ng-deep { width: 100%; height: 100%; display: block; @@ -26,6 +26,40 @@ &.invisible { visibility: hidden; } + .mat-cell { + .assignee-cell { + .assigned-container { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + .user-avatar { + display: inline-flex; + justify-content: center; + align-items: center; + margin-right: 8px; + border-radius: 50%; + width: 28px; + height: 28px; + color: white; + font-size: 13px; + font-weight: 700; + } + } + .unassigned-container { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + .material-icons.unassigned-icon { + width: 28px; + height: 28px; + font-size: 28px; + margin-right: 8px; + color: rgba(0, 0, 0, 0.38); + overflow: visible; + } + } + } + } } } span.no-data-found { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index 35392e4f9e..df2e9c634b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -93,7 +93,7 @@ import { } from '@home/components/widget/lib/display-columns-panel.component'; import { AlarmDataInfo, - alarmFields, + alarmFields, AlarmInfo, AlarmSearchStatus, alarmSeverityColors, alarmSeverityTranslations, @@ -127,6 +127,10 @@ import { entityFields } from '@shared/models/entity.models'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { ResizeObserver } from '@juggle/resize-observer'; import { hidePageSizePixelValue } from '@shared/models/constants'; +import { + ALARM_ASSIGNEE_PANEL_DATA, AlarmAssigneePanelComponent, + AlarmAssigneePanelData +} from '@home/components/alarm/alarm-assignee-panel.component'; interface AlarmsTableWidgetSettings extends TableWidgetSettings { alarmsTitle: string; @@ -418,6 +422,9 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, if (alarmField && alarmField.time) { keySettings.columnWidth = '120px'; } + if (alarmField && alarmField.keyName === alarmFields.assigneeEmail.keyName) { + keySettings.columnWidth = '120px' + } } this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings, 'value, alarm, ctx'); this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx'); @@ -966,7 +973,10 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, return alarmStatusTranslations.get(value) ? this.translate.instant(alarmStatusTranslations.get(value)) : value; } else if (alarmField.value === alarmFields.originatorType.value) { return this.translate.instant(entityTypeTranslations.get(value).type); - } else { + } else if (alarmField.value === alarmFields.assigneeEmail.value) { + return ''; + } + else { return value; } } @@ -1013,6 +1023,84 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, this.cellStyleCache.length = 0; this.rowStyleCache.length = 0; } + + getUserDisplayName(entity: AlarmInfo) { + let displayName = ''; + if ((entity.assigneeFirstName && entity.assigneeFirstName.length > 0) || + (entity.assigneeLastName && entity.assigneeLastName.length > 0)) { + if (entity.assigneeFirstName) { + displayName += entity.assigneeFirstName; + } + if (entity.assigneeLastName) { + if (displayName.length > 0) { + displayName += ' '; + } + displayName += entity.assigneeLastName; + } + } else { + displayName = entity.assigneeEmail; + } + return displayName; + } + + getUserInitials(entity: AlarmInfo): string { + let initials = ''; + if (entity.assigneeFirstName && entity.assigneeFirstName.length || + entity.assigneeLastName && entity.assigneeLastName.length) { + if (entity.assigneeFirstName) { + initials += entity.assigneeFirstName.charAt(0); + } + if (entity.assigneeLastName) { + initials += entity.assigneeLastName.charAt(0); + } + } else { + initials += entity.assigneeEmail.charAt(0); + } + return initials.toUpperCase(); + } + + getAvatarBgColor(entity: AlarmInfo) { + return this.utils.stringToHslColor(this.getUserDisplayName(entity), 40, 60); + } + + openAlarmAssigneePanel($event: Event, entity: AlarmInfo) { + if ($event) { + $event.stopPropagation(); + } + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'center', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + config.minWidth = '260px'; + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + const providers: StaticProvider[] = [ + { + provide: ALARM_ASSIGNEE_PANEL_DATA, + useValue: { + alarmId: entity.id.id, + assigneeId: entity.assigneeId?.id + } as AlarmAssigneePanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); + overlayRef.attach(new ComponentPortal(AlarmAssigneePanelComponent, + this.viewContainerRef, injector)); + } } class AlarmsDatasource implements DataSource { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 935cd507ca..2e0be36b3f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -440,9 +440,19 @@ "assignee-last-name": "Assignee last name", "assignee-email": "Assignee email", "details": "Details", + "originator-label": "Originator label", + "assign": "Assign", + "assignments": "Assignments", + "assignee": "Assignee", + "assignee-id": "Assignee id", + "assignee-first-name": "Assignee first name", + "assignee-last-name": "Assignee last name", + "assignee-email": "Assignee email", + "unassigned": "Unassigned", "status": "Status", "alarm-details": "Alarm details", "start-time": "Start time", + "assign-time": "Assign time", "end-time": "End time", "ack-time": "Acknowledged time", "clear-time": "Cleared time", @@ -480,7 +490,8 @@ "fetch-size-error-min": "Minimum value is 10.", "alarm-type-list": "Alarm type list", "any-type": "Any type", - "search-propagated-alarms": "Search propagated alarms" + "search-propagated-alarms": "Search propagated alarms", + "advanced-info": "Advanced info" }, "alias": { "add": "Add alias", From 8b6950031e4f1c122eb2aff4af14ef59f5853acf Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 17 Feb 2023 12:14:59 +0200 Subject: [PATCH 35/60] Alarm asignee updates --- .../queue/DefaultTbCoreConsumerService.java | 6 ++- .../DefaultSubscriptionManagerService.java | 50 ++++++++++--------- .../SubscriptionManagerService.java | 4 +- .../subscription/TbAlarmDataSubCtx.java | 2 +- .../subscription/TbSubscriptionUtils.java | 7 ++- .../DefaultAlarmSubscriptionService.java | 4 +- .../sub/AlarmSubscriptionUpdate.java | 18 +++---- common/cluster-api/src/main/proto/queue.proto | 2 + .../dao/alarm/AlarmOperationResult.java | 11 +++- .../common/data/alarm/AlarmAssignee.java | 34 +++++++++++++ .../data/alarm/AlarmAssigneeUpdate.java | 30 +++++++++++ .../server/common/data/query/AlarmData.java | 20 +++++++- .../server/dao/alarm/BaseAlarmService.java | 15 ++++-- 13 files changed, 150 insertions(+), 53 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssigneeUpdate.java diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 0217322375..b761ffa3fe 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -26,7 +26,6 @@ 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; @@ -36,6 +35,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.stats.StatsFactory; +import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; @@ -503,7 +503,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService(attributes)) + new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes)) , null); } } @@ -293,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, Alarm alarm, AlarmAssigneeUpdate assignee, TbCallback callback) { onLocalAlarmSubUpdate(entityId, s -> { if (TbSubscriptionType.ALARMS.equals(s.getType())) { @@ -303,14 +303,13 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene } }, s -> alarm.getCreatedTime() >= s.getTs() || alarm.getAssignTs() >= s.getTs(), - s -> alarm, - false + alarm, assignee, false ); callback.onSuccess(); } @Override - public void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarmInfo, TbCallback callback) { + public void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) { onLocalAlarmSubUpdate(entityId, s -> { if (TbSubscriptionType.ALARMS.equals(s.getType())) { @@ -319,9 +318,8 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return null; } }, - s -> alarmInfo.getCreatedTime() >= s.getTs(), - s -> alarmInfo, - true + s -> alarm.getCreatedTime() >= s.getTs(), + alarm, null, true ); callback.onSuccess(); } @@ -355,7 +353,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene deleteDeviceInactivityTimeout(tenantId, entityId, keys); } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) { clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(tenantId, - new DeviceId(entityId.getId()), scope, keys), null); + new DeviceId(entityId.getId()), scope, keys), null); } } callback.onSuccess(); @@ -415,20 +413,21 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene private void onLocalAlarmSubUpdate(EntityId entityId, Function castFunction, Predicate filterFunction, - Function processFunction, + Alarm alarm, AlarmAssigneeUpdate assignee, boolean deleted) { Set entitySubscriptions = subscriptionsByEntityId.get(entityId); + if (alarm == null) { + log.warn("[{}] empty alarm update!", entityId); + return; + } if (entitySubscriptions != null) { entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> { - Alarm alarm = processFunction.apply(s); - if (alarm != null) { - if (serviceId.equals(s.getServiceId())) { - AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, 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); - } + if (serviceId.equals(s.getServiceId())) { + AlarmSubscriptionUpdate update = new AlarmSubscriptionUpdate(s.getSubscriptionId(), alarm, assignee, deleted); + localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY); + } else { + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId()); + toCoreNotificationsProducer.send(tpi, toProto(s, alarm, assignee, deleted), null); } }); } else { @@ -559,22 +558,25 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene }); ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg( - LocalSubscriptionServiceMsgProto.newBuilder().setSubUpdate(builder.build()).build()) + LocalSubscriptionServiceMsgProto.newBuilder().setSubUpdate(builder.build()).build()) .build(); return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg); } - private TbProtoQueueMsg toProto(TbSubscription subscription, Alarm alarm, boolean deleted) { + private TbProtoQueueMsg toProto(TbSubscription subscription, Alarm alarm, AlarmAssigneeUpdate assignee, boolean deleted) { TbAlarmSubscriptionUpdateProto.Builder builder = TbAlarmSubscriptionUpdateProto.newBuilder(); builder.setSessionId(subscription.getSessionId()); builder.setSubscriptionId(subscription.getSubscriptionId()); builder.setAlarm(JacksonUtil.toString(alarm)); + if (assignee != null) { + builder.setAssignee(JacksonUtil.toString(assignee)); + } builder.setDeleted(deleted); ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToLocalSubscriptionServiceMsg( - LocalSubscriptionServiceMsgProto.newBuilder() - .setAlarmSubUpdate(builder.build()).build()) + LocalSubscriptionServiceMsgProto.newBuilder() + .setAlarmSubUpdate(builder.build()).build()) .build(); return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java index 3a5ec5a3d0..2c3423565a 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java @@ -17,12 +17,12 @@ 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; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import java.util.List; @@ -43,7 +43,7 @@ public interface SubscriptionManagerService extends ApplicationListener keys, TbCallback callback); - void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); + void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, AlarmAssigneeUpdate assignee, TbCallback callback); void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java index 64592079ee..f22fd1f12d 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java @@ -224,7 +224,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx { boolean matchesFilter = filter(alarm); if (onCurrentPage) { if (matchesFilter) { - AlarmData updated = current.update(alarm); + AlarmData updated = current.update(alarm, subscriptionUpdate.getAssignee()); updated.getLatest().putAll(current.getLatest()); alarmsMap.put(alarmId, updated); sendWsMsg(new AlarmDataUpdate(cmdId, null, Collections.singletonList(updated), maxEntitiesPerAlarmSubscription, data.getTotalElements())); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java index ac6d842501..a8b16003cb 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java +++ b/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.AlarmAssigneeUpdate; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -191,7 +192,8 @@ public class TbSubscriptionUtils { 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); + AlarmAssigneeUpdate assigneeUpdate = JacksonUtil.fromString(proto.getAlarm(), AlarmAssigneeUpdate.class); + return new AlarmSubscriptionUpdate(proto.getSubscriptionId(), alarm, assigneeUpdate, proto.getDeleted()); } } @@ -317,7 +319,7 @@ public class TbSubscriptionUtils { return entry; } - public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, Alarm alarm) { + public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, AlarmAssigneeUpdate assigneeUpdate, Alarm alarm) { TbAlarmUpdateProto.Builder builder = TbAlarmUpdateProto.newBuilder(); builder.setEntityType(entityId.getEntityType().name()); builder.setEntityIdMSB(entityId.getId().getMostSignificantBits()); @@ -325,6 +327,7 @@ public class TbSubscriptionUtils { builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); builder.setAlarm(JacksonUtil.toString(alarm)); + builder.setAssignee(JacksonUtil.toString(assigneeUpdate)); SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); msgBuilder.setAlarmUpdate(builder); return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 7c3cda0677..8c6037576f 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -211,12 +211,12 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService 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, alarm, result.getAssigneeUpdate(), 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, result.getAssigneeUpdate(), alarm); clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); } } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java index 9e2e7c16dc..201eaba9b2 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java @@ -17,16 +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; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.stream.Collectors; +import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; public class AlarmSubscriptionUpdate { @@ -39,16 +30,19 @@ public class AlarmSubscriptionUpdate { @Getter private Alarm alarm; @Getter + private AlarmAssigneeUpdate assignee; + @Getter private boolean alarmDeleted; public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm) { - this(subscriptionId, alarm, false); + this(subscriptionId, alarm, null, false); } - public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, boolean alarmDeleted) { + public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, AlarmAssigneeUpdate assignee, boolean alarmDeleted) { super(); this.subscriptionId = subscriptionId; this.alarm = alarm; + this.assignee = assignee; this.alarmDeleted = alarmDeleted; } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index d8c55c5917..75dad2bda3 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -562,6 +562,7 @@ message TbAlarmSubscriptionUpdateProto { string errorMsg = 4; string alarm = 5; bool deleted = 6; + string assignee = 7; } message TbAttributeUpdateProto { @@ -581,6 +582,7 @@ message TbAlarmUpdateProto { int64 tenantIdMSB = 4; int64 tenantIdLSB = 5; string alarm = 6; + string assignee = 7; } message TbAlarmDeleteProto { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java index 74675dda91..8f829b6d88 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java @@ -18,8 +18,8 @@ package org.thingsboard.server.dao.alarm; import lombok.AllArgsConstructor; import lombok.Data; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; 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; @@ -34,12 +34,18 @@ public class AlarmOperationResult { private final AlarmSeverity oldSeverity; private final List propagatedEntitiesList; + private final AlarmAssigneeUpdate assigneeUpdate; + + public AlarmOperationResult(Alarm alarm, AlarmAssigneeUpdate assigneeUpdate, List propagatedEntitiesList) { + this(alarm, true, false, null, propagatedEntitiesList, assigneeUpdate); + } + public AlarmOperationResult(Alarm alarm, boolean successful) { this(alarm, successful, Collections.emptyList()); } public AlarmOperationResult(Alarm alarm, boolean successful, List propagatedEntitiesList) { - this(alarm, successful, false, null, propagatedEntitiesList); + this(alarm, successful, false, null, propagatedEntitiesList, null); } public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List propagatedEntitiesList) { @@ -48,5 +54,6 @@ public class AlarmOperationResult { this.created = created; this.propagatedEntitiesList = propagatedEntitiesList; this.oldSeverity = null; + this.assigneeUpdate = null; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java new file mode 100644 index 0000000000..1b61f8c94e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.alarm; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.UserId; + +import java.io.Serializable; + +@Data +public class AlarmAssignee implements Serializable { + + private static final long serialVersionUID = 6628286223963972860L; + + private final UserId id; + private final String firstName; + private final String lastName; + private final String email; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssigneeUpdate.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssigneeUpdate.java new file mode 100644 index 0000000000..d1f32e927b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssigneeUpdate.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.alarm; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class AlarmAssigneeUpdate implements Serializable { + + private static final long serialVersionUID = -2391676304697483808L; + + private final boolean deleted; + private final AlarmAssignee assignee; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java index 59add91784..ef3e627a7b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java @@ -18,12 +18,13 @@ package org.thingsboard.server.common.data.query; import lombok.EqualsAndHashCode; import lombok.Getter; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmAssignee; +import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.EntityId; import java.util.HashMap; import java.util.Map; -import java.util.UUID; @EqualsAndHashCode(callSuper = true) public class AlarmData extends AlarmInfo { @@ -35,7 +36,7 @@ public class AlarmData extends AlarmInfo { @Getter private final Map> latest; - public AlarmData update(Alarm alarm) { + public AlarmData update(Alarm alarm, AlarmAssigneeUpdate assigneeUpdate) { this.setEndTs(alarm.getEndTs()); this.setSeverity(alarm.getSeverity()); this.setStatus(alarm.getStatus()); @@ -47,6 +48,21 @@ public class AlarmData extends AlarmInfo { // This should be changed via separate message? this.setAckTs(alarm.getAckTs()); this.setClearTs(alarm.getClearTs()); + + if (assigneeUpdate != null) { + if (assigneeUpdate.isDeleted()) { + this.setAssigneeId(null); + this.setAssigneeFirstName(null); + this.setAssigneeLastName(null); + this.setAssigneeEmail(null); + } else { + AlarmAssignee assignee = assigneeUpdate.getAssignee(); + this.setAssigneeId(assignee.getId()); + this.setAssigneeFirstName(assignee.getFirstName()); + this.setAssigneeLastName(assignee.getLastName()); + this.setAssigneeEmail(assignee.getEmail()); + } + } return this; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 814855b2a4..638ced2c34 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -30,6 +30,8 @@ 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.AlarmAssignee; +import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; @@ -228,12 +230,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } else { propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result)); } - return new AlarmOperationResult(result, true, false, oldAlarmSeverity, propagatedEntitiesList); + return new AlarmOperationResult(result, true, false, oldAlarmSeverity, propagatedEntitiesList, null); } @Override public ListenableFuture ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTime) { - return getAndUpdateAsync(tenantId, alarmId, new Function() { + return getAndUpdateAsync(tenantId, alarmId, new Function<>() { @Nullable @Override public AlarmOperationResult apply(@Nullable Alarm alarm) { @@ -286,7 +288,11 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssigneeId(assigneeId); alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); + return new AlarmOperationResult(alarm, new AlarmAssigneeUpdate(false, + new AlarmAssignee(alarmInfo.getAssigneeId(), alarmInfo.getAssigneeFirstName(), + alarmInfo.getAssigneeLastName(), alarmInfo.getAssigneeEmail()) + ), new ArrayList<>(getPropagationEntityIds(alarm))); } } }); @@ -304,7 +310,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssigneeId(null); alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + return new AlarmOperationResult(alarm, new AlarmAssigneeUpdate(true, null), + new ArrayList<>(getPropagationEntityIds(alarm))); } } }); From f877fdef3f044d5e630ef1130bf6365f590d0bc4 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 17 Feb 2023 12:37:30 +0200 Subject: [PATCH 36/60] Alarm assignee as an object --- .../server/common/data/alarm/AlarmInfo.java | 24 ++++--------------- .../server/common/data/query/AlarmData.java | 8 ++----- .../server/dao/alarm/BaseAlarmService.java | 17 +++++-------- .../server/dao/model/sql/AlarmInfoEntity.java | 7 +++--- .../dao/sql/query/AlarmDataAdapter.java | 5 ++-- .../query/DefaultAlarmQueryRepository.java | 7 ++++++ 6 files changed, 25 insertions(+), 43 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index 276c2a85b4..405097f6f5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java @@ -44,18 +44,8 @@ public class AlarmInfo extends Alarm { @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; + @ApiModelProperty(position = 21, value = "Alarm assignee") + private AlarmAssignee assignee; public AlarmInfo() { super(); @@ -69,18 +59,14 @@ public class AlarmInfo extends Alarm { super(alarmInfo); this.originatorName = alarmInfo.originatorName; this.originatorLabel = alarmInfo.originatorLabel; - this.assigneeFirstName = alarmInfo.assigneeFirstName; - this.assigneeLastName = alarmInfo.assigneeLastName; - this.assigneeEmail = alarmInfo.assigneeEmail; + this.assignee = alarmInfo.getAssignee(); } - public AlarmInfo(Alarm alarm, String originatorName, String originatorLabel, String assigneeFirstName, String assigneeLastName, String assigneeEmail) { + public AlarmInfo(Alarm alarm, String originatorName, String originatorLabel, AlarmAssignee assignee) { super(alarm); this.originatorName = originatorName; this.originatorLabel = originatorLabel; - this.assigneeFirstName = assigneeFirstName; - this.assigneeLastName = assigneeLastName; - this.assigneeEmail = assigneeEmail; + this.assignee = assignee; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java index ef3e627a7b..5e7f38cce2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java @@ -52,15 +52,11 @@ public class AlarmData extends AlarmInfo { if (assigneeUpdate != null) { if (assigneeUpdate.isDeleted()) { this.setAssigneeId(null); - this.setAssigneeFirstName(null); - this.setAssigneeLastName(null); - this.setAssigneeEmail(null); + this.setAssignee(null); } else { AlarmAssignee assignee = assigneeUpdate.getAssignee(); this.setAssigneeId(assignee.getId()); - this.setAssigneeFirstName(assignee.getFirstName()); - this.setAssigneeLastName(assignee.getLastName()); - this.setAssigneeEmail(assignee.getEmail()); + this.setAssignee(assignee); } } return this; diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 638ced2c34..218ffd2b52 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -289,10 +289,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); - return new AlarmOperationResult(alarm, new AlarmAssigneeUpdate(false, - new AlarmAssignee(alarmInfo.getAssigneeId(), alarmInfo.getAssigneeFirstName(), - alarmInfo.getAssigneeLastName(), alarmInfo.getAssigneeEmail()) - ), new ArrayList<>(getPropagationEntityIds(alarm))); + return new AlarmOperationResult(alarm, + new AlarmAssigneeUpdate(false, alarmInfo.getAssignee()), + new ArrayList<>(getPropagationEntityIds(alarm))); } } }); @@ -467,9 +466,6 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ private AlarmInfo getAlarmInfo(TenantId tenantId, Alarm alarm) { String originatorName; String originatorLabel; - String assigneeFirstName = null; - String assigneeLastName = null; - String assigneeEmail = null; Optional detailsOpt = entityService.fetchNameLabelAndCustomerDetails(tenantId, alarm.getOriginator()); if (detailsOpt.isPresent() && detailsOpt.get().getName() != null) { @@ -481,13 +477,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ originatorLabel = "Deleted"; } + AlarmAssignee assignee = null; if (alarm.getAssigneeId() != null) { User assignedUser = userService.findUserById(tenantId, alarm.getAssigneeId()); - assigneeFirstName = assignedUser.getFirstName(); - assigneeLastName = assignedUser.getLastName(); - assigneeEmail = assignedUser.getEmail(); + assignee = new AlarmAssignee(assignedUser.getId(), assignedUser.getFirstName(), assignedUser.getLastName(), assignedUser.getEmail()); } - return new AlarmInfo(alarm, originatorName, originatorLabel, assigneeFirstName, assigneeLastName, assigneeEmail); + return new AlarmInfo(alarm, originatorName, originatorLabel, assignee); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java index 09296f3449..0587cdb4ab 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java @@ -17,7 +17,9 @@ package org.thingsboard.server.dao.model.sql; import lombok.Data; import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.alarm.AlarmAssignee; import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.id.UserId; @Data @EqualsAndHashCode(callSuper = true) @@ -47,10 +49,7 @@ public class AlarmInfoEntity extends AbstractAlarmEntity { AlarmInfo alarmInfo = new AlarmInfo(super.toAlarm()); alarmInfo.setOriginatorName(originatorName); alarmInfo.setOriginatorLabel(originatorLabel); - - alarmInfo.setAssigneeFirstName(assigneeFirstName); - alarmInfo.setAssigneeLastName(assigneeLastName); - alarmInfo.setAssigneeEmail(assigneeEmail); + alarmInfo.setAssignee(new AlarmAssignee(new UserId(getAssigneeId()), assigneeFirstName, assigneeLastName, assigneeEmail)); return alarmInfo; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java index f86f3d0e8f..3a80d83fb5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java @@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmAssignee; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.AlarmId; @@ -123,9 +124,7 @@ public class AlarmDataAdapter { AlarmData alarmData = new AlarmData(alarm, entityId); alarmData.setOriginatorName(originatorName); alarmData.setOriginatorLabel(originatorLabel); - alarmData.setAssigneeFirstName(assigneeFirstName); - alarmData.setAssigneeLastName(assigneeLastName); - alarmData.setAssigneeEmail(assigneeEmail); + alarmData.setAssignee(new AlarmAssignee(alarm.getAssigneeId(), assigneeFirstName, assigneeLastName, assigneeEmail)); return alarmData; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index 91c0694422..67c759d6cf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.dao.model.ModelConstants; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -186,6 +187,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { } fromPart.append(LEFT_JOIN_TB_USERS); EntityDataSortOrder sortOrder = pageLink.getSortOrder(); + + List queryFields = new ArrayList<>(); + for(EntityKey key: query.getAlarmFields()){ + if() + } + String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch()); if (sortOrder != null && sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) { String sortOrderKey = sortOrder.getKey().getKey(); From b9f5e45fa0dfa8bfec32d876361dea43ec6100fb Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 17 Feb 2023 12:43:02 +0200 Subject: [PATCH 37/60] Assignee query keys --- .../query/DefaultAlarmQueryRepository.java | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index 67c759d6cf..59c01c5ae1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -52,6 +52,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { private static final Map alarmFieldColumnMap = new HashMap<>(); + private static final String ASSIGNEE_EMAIL_KEY = "assigneeEmail"; + private static final String ASSIGNEE_LAST_NAME_KEY = "assigneeLastName"; + private static final String ASSIGNEE_FIRST_NAME_KEY = "assigneeFirstName"; + private static final String ASSIGNEE_ID_KEY = "assigneeId"; + private static final String ASSIGNEE_KEY = "assignee"; + static { alarmFieldColumnMap.put("createdTime", ModelConstants.CREATED_TIME_PROPERTY); alarmFieldColumnMap.put("ackTs", ModelConstants.ALARM_ACK_TS_PROPERTY); @@ -69,12 +75,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("assigneeId", ModelConstants.ALARM_ASSIGNEE_ID_PROPERTY); + alarmFieldColumnMap.put(ASSIGNEE_ID_KEY, 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); + alarmFieldColumnMap.put(ASSIGNEE_FIRST_NAME_KEY, ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY); + alarmFieldColumnMap.put(ASSIGNEE_LAST_NAME_KEY, ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY); + alarmFieldColumnMap.put(ASSIGNEE_EMAIL_KEY, ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY); } private static final String SELECT_ORIGINATOR_NAME = " COALESCE(CASE" + @@ -188,12 +194,19 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { fromPart.append(LEFT_JOIN_TB_USERS); EntityDataSortOrder sortOrder = pageLink.getSortOrder(); - List queryFields = new ArrayList<>(); - for(EntityKey key: query.getAlarmFields()){ - if() + List alarmFields = new ArrayList<>(); + for (EntityKey key : query.getAlarmFields()) { + if (EntityKeyType.ALARM_FIELD.equals(key.getType()) && ASSIGNEE_KEY.equalsIgnoreCase(key.getKey())) { + alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, ASSIGNEE_ID_KEY)); + alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, ASSIGNEE_FIRST_NAME_KEY)); + alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, ASSIGNEE_LAST_NAME_KEY)); + alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, ASSIGNEE_EMAIL_KEY)); + } else { + alarmFields.add(key); + } } - String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch()); + String textSearchQuery = buildTextSearchQuery(ctx, alarmFields, pageLink.getTextSearch()); if (sortOrder != null && sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) { String sortOrderKey = sortOrder.getKey().getKey(); sortPart.append(alarmFieldColumnMap.getOrDefault(sortOrderKey, sortOrderKey)) @@ -288,7 +301,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { } } - if (pageLink.getAssigneeId() != null){ + if (pageLink.getAssigneeId() != null) { ctx.addUuidParameter("assigneeId", pageLink.getAssigneeId().getId()); wherePart.append(" a.assignee_id = :assigneeId"); } From c16dfd440767f9161325b8e0ed66bacb88841418 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 17 Feb 2023 16:15:17 +0200 Subject: [PATCH 38/60] Alarm query updates --- .../server/controller/BaseController.java | 2 +- .../DefaultAlarmSubscriptionService.java | 4 +- .../server/dao/alarm/AlarmService.java | 35 +++- .../data/alarm/AlarmPropagationInfo.java | 37 +++++ .../server/common/data/alarm/AlarmQuery.java | 1 + .../common/data/alarm/AlarmUpdateRequest.java | 45 +++++ .../CreateOrUpdateActiveAlarmRequest.java | 50 ++++++ .../server/dao/alarm/AlarmDao.java | 2 + .../server/dao/alarm/BaseAlarmService.java | 154 +++++------------- .../server/dao/model/ModelConstants.java | 1 + .../server/dao/model/sql/AlarmInfoEntity.java | 27 ++- .../server/dao/sql/alarm/AlarmRepository.java | 21 ++- .../server/dao/sql/alarm/JpaAlarmDao.java | 5 + .../query/DefaultAlarmQueryRepository.java | 4 +- .../resources/sql/schema-entities-idx.sql | 3 + .../main/resources/sql/schema-entities.sql | 31 +++- .../engine/api/RuleEngineAlarmService.java | 7 +- 17 files changed, 290 insertions(+), 139 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/CreateOrUpdateActiveAlarmRequest.java diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index c6a3ac6105..8505b710d5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -724,7 +724,7 @@ public abstract class BaseController { AlarmInfo checkAlarmInfoId(AlarmId alarmId, Operation operation) throws ThingsboardException { try { validateId(alarmId, "Incorrect alarmId " + alarmId); - AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); + AlarmInfo alarmInfo = alarmService.findAlarmInfoById(getCurrentUser().getTenantId(), alarmId); checkNotNull(alarmInfo, "Alarm with id [" + alarmId + "] is not found"); accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarmInfo); return alarmInfo; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 8c6037576f..71d58b48a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -174,8 +174,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService } @Override - public ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) { - return alarmService.findAlarmInfoByIdAsync(tenantId, alarmId); + public AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId) { + return alarmService.findAlarmInfoById(tenantId, alarmId); } @Override diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index 2e5660e1c8..e160053ff9 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -23,6 +23,8 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; +import org.thingsboard.server.common.data.alarm.CreateOrUpdateActiveAlarmRequest; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -35,30 +37,51 @@ import org.thingsboard.server.dao.entity.EntityDaoService; import java.util.Collection; -/** - * Created by ashvayka on 11.05.17. - */ + public interface AlarmService extends EntityDaoService { + // New API, since 3.5. + /** + * Designed for atomic operations over active alarms. + * Only one active alarm may exist for the pair {originatorId, alarmType} + */ + AlarmOperationResult createAlarm(CreateOrUpdateActiveAlarmRequest request); + + /** + * Designed for atomic operations over active alarms. + * Only one active alarm may exist for the pair {originatorId, alarmType} + */ + AlarmOperationResult createAlarm(CreateOrUpdateActiveAlarmRequest request, boolean alarmCreationEnabled); + + /** + * Designed to update existing alarm. Accepts only part of the alarm fields. + * + */ + AlarmOperationResult updateAlarm(AlarmUpdateRequest request); + + // Legacy API, before 3.5 + AlarmOperationResult createOrUpdateAlarm(Alarm alarm); AlarmOperationResult createOrUpdateAlarm(Alarm alarm, boolean alarmCreationEnabled); + // Other API + AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId); ListenableFuture ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs); ListenableFuture clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs); - AlarmOperationResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs); + AlarmOperationResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long ts); - AlarmOperationResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs); + AlarmOperationResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long ts); Alarm findAlarmById(TenantId tenantId, AlarmId alarmId); ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId); - ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId); + AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId); ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java new file mode 100644 index 0000000000..78d62de523 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.alarm; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class AlarmPropagationInfo { + + @ApiModelProperty(position = 1, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") + private boolean propagate; + @ApiModelProperty(position = 2, 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 = 3, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true") + private boolean propagateToTenant; + @ApiModelProperty(position = 4, 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 propagateRelationTypes; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java index d6af82f413..e4d0fb32df 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java @@ -35,6 +35,7 @@ public class AlarmQuery { private AlarmSearchStatus searchStatus; private AlarmStatus status; private UserId assigneeId; + @Deprecated private Boolean fetchOriginator; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java new file mode 100644 index 0000000000..a5d88edb41 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.alarm; + +import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.TenantId; + +@Data +public class AlarmUpdateRequest { + + @ApiModelProperty(position = 1, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + private TenantId tenantId; + @ApiModelProperty(position = 2, value = "JSON object with the alarm Id. " + + "Specify this field to update the alarm. " + + "Referencing non-existing alarm Id will cause error. " + + "Omit this field to create new alarm.") + private AlarmId alarmId; + @ApiModelProperty(position = 3, required = true, value = "Alarm severity", example = "CRITICAL") + private AlarmSeverity severity; + @ApiModelProperty(position = 4, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") + private long startTs; + @ApiModelProperty(position = 5, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") + private long endTs; + @ApiModelProperty(position = 6, value = "JSON object with alarm details") + private JsonNode details; + @ApiModelProperty(position = 7, value = "JSON object with propagation details") + private AlarmPropagationInfo propagation; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/CreateOrUpdateActiveAlarmRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/CreateOrUpdateActiveAlarmRequest.java new file mode 100644 index 0000000000..afd4a2e537 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/CreateOrUpdateActiveAlarmRequest.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.alarm; + +import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.validation.Length; + +@Data +public class CreateOrUpdateActiveAlarmRequest { + + @ApiModelProperty(position = 1, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + private TenantId tenantId; + @ApiModelProperty(position = 2, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + private CustomerId customerId; + @ApiModelProperty(position = 3, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm") + @Length(fieldName = "type") + private String type; + @ApiModelProperty(position = 4, required = true, value = "JSON object with alarm originator id") + private EntityId originator; + @ApiModelProperty(position = 5, required = true, value = "Alarm severity", example = "CRITICAL") + private AlarmSeverity severity; + @ApiModelProperty(position = 6, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") + private long startTs; + @ApiModelProperty(position = 7, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") + private long endTs; + @ApiModelProperty(position = 8, value = "JSON object with alarm details") + private JsonNode details; + @ApiModelProperty(position = 9, value = "JSON object with propagation details") + private AlarmPropagationInfo propagation; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index fa1a8a2eb9..daa8d2e206 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -51,6 +51,8 @@ public interface AlarmDao extends Dao { ListenableFuture findAlarmByIdAsync(TenantId tenantId, UUID key); + AlarmInfo findAlarmInfoById(TenantId tenantId, UUID key); + Alarm save(TenantId tenantId, Alarm alarm); PageData findAlarms(TenantId tenantId, AlarmQuery query); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 218ffd2b52..4ae0954816 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -26,17 +26,18 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; 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.AlarmAssignee; import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; +import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.alarm.CreateOrUpdateActiveAlarmRequest; import org.thingsboard.server.common.data.alarm.EntityAlarm; import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException; import org.thingsboard.server.common.data.id.AlarmId; @@ -59,8 +60,6 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.user.UserService; import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -69,8 +68,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -90,24 +87,23 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Autowired private EntityService entityService; - @Autowired - private UserService userService; - @Autowired private DataValidator alarmDataValidator; - protected ExecutorService readResultsProcessingExecutor; - @PostConstruct - public void startExecutor() { - readResultsProcessingExecutor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName("alarm-service")); + @Override + public AlarmOperationResult updateAlarm(AlarmUpdateRequest request) { + return null; } - @PreDestroy - public void stopExecutor() { - if (readResultsProcessingExecutor != null) { - readResultsProcessingExecutor.shutdownNow(); - } + @Override + public AlarmOperationResult createAlarm(CreateOrUpdateActiveAlarmRequest request) { + return null; + } + + @Override + public AlarmOperationResult createAlarm(CreateOrUpdateActiveAlarmRequest request, boolean alarmCreationEnabled) { + return null; } @Override @@ -235,45 +231,35 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override public ListenableFuture ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTime) { - return getAndUpdateAsync(tenantId, alarmId, new Function<>() { - @Nullable - @Override - public AlarmOperationResult apply(@Nullable Alarm alarm) { - if (alarm == null || alarm.getStatus().isAck()) { - return new AlarmOperationResult(alarm, false); - } else { - AlarmStatus oldStatus = alarm.getStatus(); - AlarmStatus newStatus = oldStatus.isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK; - alarm.setStatus(newStatus); - alarm.setAckTs(ackTime); - alarm = alarmDao.save(alarm.getTenantId(), alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); - } - } - }); + Alarm alarm = alarmDao.findAlarmById(tenantId, alarmId.getId()); + if (alarm == null || alarm.getStatus().isAck()) { + return Futures.immediateFuture(new AlarmOperationResult(alarm, false)); + } else { + AlarmStatus oldStatus = alarm.getStatus(); + AlarmStatus newStatus = oldStatus.isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK; + alarm.setStatus(newStatus); + alarm.setAckTs(ackTime); + alarm = alarmDao.save(alarm.getTenantId(), alarm); + return Futures.immediateFuture(new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)))); + } } @Override public ListenableFuture clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTime) { - return getAndUpdateAsync(tenantId, alarmId, new Function() { - @Nullable - @Override - public AlarmOperationResult apply(@Nullable Alarm alarm) { - if (alarm == null || alarm.getStatus().isCleared()) { - return new AlarmOperationResult(alarm, false); - } else { - AlarmStatus oldStatus = alarm.getStatus(); - AlarmStatus newStatus = oldStatus.isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK; - alarm.setStatus(newStatus); - alarm.setClearTs(clearTime); - if (details != null) { - alarm.setDetails(details); - } - alarm = alarmDao.save(alarm.getTenantId(), alarm); - return new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); - } + Alarm alarm = alarmDao.findAlarmById(tenantId, alarmId.getId()); + if (alarm == null || alarm.getStatus().isCleared()) { + return Futures.immediateFuture(new AlarmOperationResult(alarm, false)); + } else { + AlarmStatus oldStatus = alarm.getStatus(); + AlarmStatus newStatus = oldStatus.isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK; + alarm.setStatus(newStatus); + alarm.setClearTs(clearTime); + if (details != null) { + alarm.setDetails(details); } - }); + alarm = alarmDao.save(alarm.getTenantId(), alarm); + return Futures.immediateFuture(new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)))); + } } @Override @@ -288,7 +274,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ alarm.setAssigneeId(assigneeId); alarm.setAssignTs(assignTime); alarm = alarmDao.save(alarm.getTenantId(), alarm); - AlarmInfo alarmInfo = getAlarmInfo(tenantId, alarm); + AlarmInfo alarmInfo = alarmDao.findAlarmInfoById(tenantId, alarm.getUuidId()); return new AlarmOperationResult(alarm, new AlarmAssigneeUpdate(false, alarmInfo.getAssignee()), new ArrayList<>(getPropagationEntityIds(alarm))); @@ -331,48 +317,20 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } @Override - public ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) { + public AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId) { log.trace("Executing findAlarmInfoByIdAsync [{}]", alarmId); validateId(alarmId, "Incorrect alarmId " + alarmId); - return Futures.transform(alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()), - a -> getAlarmInfo(tenantId, a), MoreExecutors.directExecutor()); + return alarmDao.findAlarmInfoById(tenantId, alarmId.getId()); } @Override public ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query) { - PageData alarms = alarmDao.findAlarms(tenantId, query); - if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { - return fetchAlarmsOriginators(tenantId, alarms); - } - return Futures.immediateFuture(alarms); + return Futures.immediateFuture(alarmDao.findAlarms(tenantId, query)); } @Override public ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) { - PageData alarms = alarmDao.findCustomerAlarms(tenantId, customerId, query); - if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { - return fetchAlarmsOriginators(tenantId, alarms); - } - return Futures.immediateFuture(alarms); - } - - private ListenableFuture> fetchAlarmsOriginators(TenantId tenantId, PageData alarms) { - List> alarmFutures = new ArrayList<>(alarms.getData().size()); - for (AlarmInfo alarmInfo : alarms.getData()) { - Optional detailsOpt = entityService.fetchNameLabelAndCustomerDetails(tenantId, alarmInfo.getOriginator()); - if (detailsOpt.isPresent() && detailsOpt.get().getName() != null) { - NameLabelAndCustomerDetails details = detailsOpt.get(); - alarmInfo.setOriginatorName(details.getName()); - alarmInfo.setOriginatorLabel(details.getLabel()); - } else { - alarmInfo.setOriginatorName("Deleted"); - alarmInfo.setOriginatorLabel("Deleted"); - } - alarmFutures.add(Futures.immediateFuture(alarmInfo)); - } - return Futures.transform(Futures.successfulAsList(alarmFutures), - alarmInfos -> new PageData<>(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), - alarms.hasNext()), MoreExecutors.directExecutor()); + return Futures.immediateFuture(alarmDao.findCustomerAlarms(tenantId, customerId, query)); } @Override @@ -451,40 +409,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } } - private ListenableFuture getAndUpdateAsync(TenantId tenantId, AlarmId alarmId, Function function) { - validateId(alarmId, "Alarm id should be specified!"); - ListenableFuture entity = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()); - return Futures.transform(entity, function, readResultsProcessingExecutor); - } - private T getAndUpdate(TenantId tenantId, AlarmId alarmId, Function function) { validateId(alarmId, "Alarm id should be specified!"); Alarm entity = alarmDao.findAlarmById(tenantId, alarmId.getId()); return function.apply(entity); } - private AlarmInfo getAlarmInfo(TenantId tenantId, Alarm alarm) { - String originatorName; - String originatorLabel; - - Optional detailsOpt = entityService.fetchNameLabelAndCustomerDetails(tenantId, alarm.getOriginator()); - if (detailsOpt.isPresent() && detailsOpt.get().getName() != null) { - NameLabelAndCustomerDetails details = detailsOpt.get(); - originatorName = details.getName(); - originatorLabel = details.getLabel(); - } else { - originatorName = "Deleted"; - originatorLabel = "Deleted"; - } - - AlarmAssignee assignee = null; - if (alarm.getAssigneeId() != null) { - User assignedUser = userService.findUserById(tenantId, alarm.getAssigneeId()); - assignee = new AlarmAssignee(assignedUser.getId(), assignedUser.getFirstName(), assignedUser.getLastName(), assignedUser.getEmail()); - } - return new AlarmInfo(alarm, originatorName, originatorLabel, assignee); - } - @Override public Optional> findEntity(TenantId tenantId, EntityId entityId) { return Optional.ofNullable(findAlarmById(tenantId, new AlarmId(entityId.getId()))); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 33df276a46..a4eb8fb3ae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -287,6 +287,7 @@ public class ModelConstants { */ public static final String ENTITY_ALARM_COLUMN_FAMILY_NAME = "entity_alarm"; public static final String ALARM_COLUMN_FAMILY_NAME = "alarm"; + public static final String ALARM_VIEW_NAME = "alarm_info"; public static final String ALARM_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String ALARM_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; public static final String ALARM_TYPE_PROPERTY = "type"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java index 0587cdb4ab..43483a0165 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java @@ -21,23 +21,44 @@ import org.thingsboard.server.common.data.alarm.AlarmAssignee; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.UserId; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CUSTOMER_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_VIEW_NAME; + @Data @EqualsAndHashCode(callSuper = true) +@Entity +@Table(name = ALARM_VIEW_NAME) public class AlarmInfoEntity extends AbstractAlarmEntity { + @Column(name = ALARM_ORIGINATOR_NAME_PROPERTY) private String originatorName; + @Column(name = ALARM_ORIGINATOR_LABEL_PROPERTY) private String originatorLabel; - + @Column(name = ALARM_ASSIGNEE_FIRST_NAME_PROPERTY) private String assigneeFirstName; + @Column(name = ALARM_ASSIGNEE_LAST_NAME_PROPERTY) private String assigneeLastName; + @Column(name = ALARM_ASSIGNEE_EMAIL_PROPERTY) private String assigneeEmail; public AlarmInfoEntity() { super(); } - public AlarmInfoEntity(AlarmEntity alarmEntity, String assigneeFirstName, - String assigneeLastName, String assigneeEmail) { + public AlarmInfoEntity(AlarmEntity alarmEntity, + String assigneeFirstName, + String assigneeLastName, + String assigneeEmail) { super(alarmEntity); this.assigneeFirstName = assigneeFirstName; this.assigneeLastName = assigneeLastName; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index ba61e89a6d..751d876296 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -39,10 +39,9 @@ public interface AlarmRepository extends JpaRepository { @Param("alarmType") String alarmType, Pageable pageable); - @Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a, tbu.firstName, tbu.lastName, tbu.email) " + - "FROM AlarmEntity a " + + @Query(value = "SELECT a " + + "FROM AlarmInfoEntity 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 " + @@ -57,7 +56,7 @@ public interface AlarmRepository extends JpaRepository { , countQuery = "" + "SELECT count(a) " + //alarms with relations only - "FROM AlarmEntity a " + + "FROM AlarmInfoEntity a " + "LEFT JOIN EntityAlarmEntity ea ON a.id = ea.alarmId " + "WHERE a.tenantId = :tenantId " + "AND ea.tenantId = :tenantId " + @@ -80,9 +79,8 @@ public interface AlarmRepository extends JpaRepository { @Param("searchText") String searchText, Pageable pageable); - @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 " + + @Query(value = "SELECT a " + + "FROM AlarmInfoEntity a " + "WHERE a.tenantId = :tenantId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + @@ -110,9 +108,8 @@ public interface AlarmRepository extends JpaRepository { @Param("searchText") String searchText, Pageable pageable); - @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 " + + @Query(value = "SELECT a " + + "FROM AlarmInfoEntity a " + "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + @@ -124,7 +121,7 @@ public interface AlarmRepository extends JpaRepository { , countQuery = "" + "SELECT count(a) " + - "FROM AlarmEntity a " + + "FROM AlarmInfoEntity a " + "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + @@ -159,4 +156,6 @@ public interface AlarmRepository extends JpaRepository { @Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.createdTime < :time AND a.endTs < :time") Page findAlarmsIdsByEndTsBeforeAndTenantId(@Param("time") Long time, @Param("tenantId") UUID tenantId, Pageable pageable); + @Query(value = "SELECT a FROM AlarmInfoEntity a WHERE a.tenantId = :tenantId AND a.id = :alarmId") + AlarmInfoEntity findAlarmInfoById(@Param("tenantId") UUID tenantId, @Param("alarmId") UUID alarmId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 9c5bfb6a55..21d4990bd0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -100,6 +100,11 @@ public class JpaAlarmDao extends JpaAbstractDao implements A return findById(tenantId, key); } + @Override + public AlarmInfo findAlarmInfoById(TenantId tenantId, UUID key) { + return DaoUtil.getData(alarmRepository.findAlarmInfoById(tenantId.getId(), key)); + } + @Override public ListenableFuture findAlarmByIdAsync(TenantId tenantId, UUID key) { return findByIdAsync(tenantId, key); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index 59c01c5ae1..a56273c785 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -83,7 +83,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { alarmFieldColumnMap.put(ASSIGNEE_EMAIL_KEY, ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY); } - private static final String SELECT_ORIGINATOR_NAME = " COALESCE(CASE" + + public static final String SELECT_ORIGINATOR_NAME = " 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() + @@ -106,7 +106,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { " 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" + + public 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() + diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index 3671b4ed34..79edc1e86b 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -28,6 +28,9 @@ CREATE INDEX IF NOT EXISTS idx_alarm_tenant_assignee_created_time ON alarm(tenan CREATE INDEX IF NOT EXISTS idx_entity_alarm_created_time ON entity_alarm(tenant_id, entity_id, created_time DESC); +CREATE INDEX IF NOT EXISTS idx_entity_alarm_entity_id_alarm_type_created_time_alarm_id ON entity_alarm +USING btree (tenant_id, entity_id, alarm_type, created_time DESC) INCLUDE(alarm_id); + CREATE INDEX IF NOT EXISTS idx_entity_alarm_alarm_id ON entity_alarm(alarm_id); CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index f592ff6446..0120902e88 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -797,4 +797,33 @@ CREATE TABLE IF NOT EXISTS user_settings ( user_id uuid NOT NULL CONSTRAINT user_settings_pkey PRIMARY KEY, settings varchar(10000), CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE -); \ No newline at end of file +); + +DROP VIEW IF EXISTS alarm_info; +CREATE VIEW alarm_info AS +SELECT a.*, +COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id) + WHEN a.originator_type = 1 THEN (select title from customer where id = a.originator_id) + WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id) + WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id) + WHEN a.originator_type = 4 THEN (select name from asset where id = a.originator_id) + WHEN a.originator_type = 5 THEN (select name from device where id = a.originator_id) + WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id) + WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id) + WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id) + WHEN a.originator_type = 18 THEN (select name from edge where id = a.originator_id) END + , 'Deleted') originator_name, +COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id) + WHEN a.originator_type = 1 THEN (select COALESCE(title, email) from customer where id = a.originator_id) + WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id) + WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id) + WHEN a.originator_type = 4 THEN (select COALESCE(label, name) from asset where id = a.originator_id) + WHEN a.originator_type = 5 THEN (select COALESCE(label, name) from device where id = a.originator_id) + WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id) + WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id) + WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id) + WHEN a.originator_type = 18 THEN (select COALESCE(label, name) from edge where id = a.originator_id) END + , 'Deleted') as originator_label, +u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email +FROM alarm a +LEFT JOIN tb_user u ON u.id = a.assignee_id; \ No newline at end of file diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java index cf35729de7..c111a498c3 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.api; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -60,7 +61,11 @@ public interface RuleEngineAlarmService { ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type); - ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId); + AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId); + + default ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId){ + return Futures.immediateFuture(findAlarmInfoById(tenantId, alarmId)); + } ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query); From aa18dde0dc05bfe913eeef1d27890b67411c883d Mon Sep 17 00:00:00 2001 From: zbeacon Date: Sat, 18 Feb 2023 12:15:01 +0200 Subject: [PATCH 39/60] Refactored method name --- .../java/org/thingsboard/server/controller/AlarmController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 005d474c96..dfac4c5c39 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -217,7 +217,7 @@ public class AlarmController extends BaseController { @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) + public Alarm unassignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId ) throws Exception { checkParameter(ALARM_ID, strAlarmId); From 0427eeea9d8b6dd4bdd692a834a9db544b526cbe Mon Sep 17 00:00:00 2001 From: zbeacon Date: Sat, 18 Feb 2023 13:07:32 +0200 Subject: [PATCH 40/60] Added check - does assigned information available --- .../thingsboard/server/common/data/alarm/AlarmAssignee.java | 1 - .../thingsboard/server/dao/model/sql/AlarmInfoEntity.java | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java index 1b61f8c94e..abe8626ad7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.common.data.alarm; -import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.id.UserId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java index 43483a0165..aa58e9e469 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java @@ -28,8 +28,6 @@ import javax.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CUSTOMER_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_VIEW_NAME; @@ -70,7 +68,9 @@ public class AlarmInfoEntity extends AbstractAlarmEntity { AlarmInfo alarmInfo = new AlarmInfo(super.toAlarm()); alarmInfo.setOriginatorName(originatorName); alarmInfo.setOriginatorLabel(originatorLabel); - alarmInfo.setAssignee(new AlarmAssignee(new UserId(getAssigneeId()), assigneeFirstName, assigneeLastName, assigneeEmail)); + if (getAssigneeId() != null) { + alarmInfo.setAssignee(new AlarmAssignee(new UserId(getAssigneeId()), assigneeFirstName, assigneeLastName, assigneeEmail)); + } return alarmInfo; } } From 80ec4d85e2f9b9b00a757b3bf9afa6d1b65343a4 Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 20 Feb 2023 14:13:41 +0200 Subject: [PATCH 41/60] Amendments due to backend changes, added ability to search users by name and email, added setting to disable reassignment of alarm to alarm widget --- ui-ngx/src/app/core/http/user.service.ts | 6 ++- .../alarm/alarm-assignee-panel.component.html | 7 +-- .../alarm/alarm-assignee-panel.component.scss | 21 +++++---- .../alarm/alarm-assignee-panel.component.ts | 10 ++--- .../alarm/alarm-details-dialog.component.ts | 43 ++++++------------- .../components/alarm/alarm-table-config.ts | 32 +++++++------- .../alarm/alarm-table.component.scss | 6 +++ .../lib/alarms-table-widget.component.html | 11 +++-- .../lib/alarms-table-widget.component.scss | 2 + .../lib/alarms-table-widget.component.ts | 35 ++++++++------- ...larms-table-widget-settings.component.html | 3 ++ .../alarms-table-widget-settings.component.ts | 2 + ui-ngx/src/app/shared/models/alarm.models.ts | 41 +++++++----------- ui-ngx/src/app/shared/models/user.model.ts | 7 +++ .../assets/locale/locale.constant-en_US.json | 8 +--- 15 files changed, 120 insertions(+), 114 deletions(-) diff --git a/ui-ngx/src/app/core/http/user.service.ts b/ui-ngx/src/app/core/http/user.service.ts index 0a7d2054e4..9a6b70ce29 100644 --- a/ui-ngx/src/app/core/http/user.service.ts +++ b/ui-ngx/src/app/core/http/user.service.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { User } from '@shared/models/user.model'; +import { User, UserEmailInfo } from '@shared/models/user.model'; import { Observable } from 'rxjs'; import { HttpClient, HttpParams } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; @@ -84,4 +84,8 @@ export class UserService { return this.http.post(url, null, defaultHttpOptionsFromConfig(config)); } + public findUsersByQuery(pageLink: PageLink, config?: RequestConfig) : Observable> { + return this.http.get>(`/api/users/info${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); + } + } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html index 262a45e435..2213be1835 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html @@ -36,9 +36,10 @@ -
- - +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss index 462df2e0d2..c76aedbb18 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss @@ -76,19 +76,22 @@ background-color: #5cb445; width: 28px; height: 28px; + min-width: 28px; + min-height: 28px; color: white; font-size: 13px; font-weight: 700 } - .user-email { - color: rgba(0, 0, 0, 0.76); - overflow: hidden; - text-overflow: ellipsis; - max-width: 185px - } - .user-name { - color: rgba(0, 0, 0, 0.38); - font-size: 13px; + .user-display-name { + max-width: 180px; + span { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + span + span { + color: rgba(0, 0, 0, 0.38); + } } .mat-option-text { display: flex; diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts index 910a6f57c0..32d6c2a1da 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts @@ -34,7 +34,7 @@ import { switchMap, takeUntil, } from 'rxjs/operators'; -import { User } from '@shared/models/user.model'; +import { User, UserEmailInfo } from '@shared/models/user.model'; import { TranslateService } from '@ngx-translate/core'; import { UserService } from '@core/http/user.service'; import { PageLink } from '@shared/models/page/page-link'; @@ -69,7 +69,7 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe @ViewChild('userInput', {static: true}) userInput: ElementRef; - filteredUsers: Observable>; + filteredUsers: Observable>; searchText = ''; @@ -138,15 +138,15 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe () => this.overlayRef.dispose()); } - fetchUsers(searchText?: string): Observable> { + fetchUsers(searchText?: string): Observable> { this.searchText = searchText; const pageLink = new PageLink(50, 0, searchText, { property: 'email', direction: Direction.ASC }); - return this.userService.getUsers(pageLink, {ignoreLoading: true}) + return this.userService.findUsersByQuery(pageLink, {ignoreLoading: true}) .pipe( - catchError(() => of(emptyPageData())), + catchError(() => of(emptyPageData())), map(pageData => { return pageData.data; }) diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts index 2e97ce7405..d609bef616 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts @@ -88,9 +88,6 @@ export class AlarmDetailsDialogComponent extends DialogComponent 0) || - (entity.assigneeLastName && entity.assigneeLastName.length > 0)) { - if (entity.assigneeFirstName) { - displayName += entity.assigneeFirstName; + if ((entity.assignee.firstName && entity.assignee.firstName.length > 0) || + (entity.assignee.lastName && entity.assignee.lastName.length > 0)) { + if (entity.assignee.firstName) { + displayName += entity.assignee.firstName; } - if (entity.assigneeLastName) { + if (entity.assignee.lastName) { if (displayName.length > 0) { displayName += ' '; } - displayName += entity.assigneeLastName; + displayName += entity.assignee.lastName; } } else { - displayName = entity.assigneeEmail; + displayName = entity.assignee.email; } return displayName; } getUserInitials(entity: AlarmInfo): string { let initials = ''; - if (entity.assigneeFirstName && entity.assigneeFirstName.length || - entity.assigneeLastName && entity.assigneeLastName.length) { - if (entity.assigneeFirstName) { - initials += entity.assigneeFirstName.charAt(0); + if (entity.assignee.firstName && entity.assignee.firstName.length || + entity.assignee.lastName && entity.assignee.lastName.length) { + if (entity.assignee.firstName) { + initials += entity.assignee.firstName.charAt(0); } - if (entity.assigneeLastName) { - initials += entity.assigneeLastName.charAt(0); + if (entity.assignee.lastName) { + initials += entity.assignee.lastName.charAt(0); } } else { - initials += entity.assigneeEmail.charAt(0); + initials += entity.assignee.email.charAt(0); } return initials.toUpperCase(); } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index f29dae1926..645841f188 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -116,7 +116,7 @@ export class AlarmTableConfig extends EntityTableConfig color: alarmSeverityColors.get(entity.severity) }))); this.columns.push( - new EntityTableColumn('assigneeEmail', 'alarm.assignee', '200px', + new EntityTableColumn('assignee', 'alarm.assignee', '200px', (entity) => { return this.getAssigneeTemplate(entity) }, @@ -190,7 +190,7 @@ export class AlarmTableConfig extends EntityTableConfig ${this.getUserInitials(entity)} - ${this.getUserDisplayName(entity)} + ${this.getUserDisplayName(entity)} ` : `account_circle @@ -201,35 +201,35 @@ export class AlarmTableConfig extends EntityTableConfig getUserDisplayName(entity: AlarmInfo) { let displayName = ''; - if ((entity.assigneeFirstName && entity.assigneeFirstName.length > 0) || - (entity.assigneeLastName && entity.assigneeLastName.length > 0)) { - if (entity.assigneeFirstName) { - displayName += entity.assigneeFirstName; + if ((entity.assignee.firstName && entity.assignee.firstName.length > 0) || + (entity.assignee.lastName && entity.assignee.lastName.length > 0)) { + if (entity.assignee.firstName) { + displayName += entity.assignee.firstName; } - if (entity.assigneeLastName) { + if (entity.assignee.lastName) { if (displayName.length > 0) { displayName += ' '; } - displayName += entity.assigneeLastName; + displayName += entity.assignee.lastName; } } else { - displayName = entity.assigneeEmail; + displayName = entity.assignee.email; } return displayName; } getUserInitials(entity: AlarmInfo): string { let initials = ''; - if (entity.assigneeFirstName && entity.assigneeFirstName.length || - entity.assigneeLastName && entity.assigneeLastName.length) { - if (entity.assigneeFirstName) { - initials += entity.assigneeFirstName.charAt(0); + if (entity.assignee.firstName && entity.assignee.firstName.length || + entity.assignee.lastName && entity.assignee.lastName.length) { + if (entity.assignee.firstName) { + initials += entity.assignee.firstName.charAt(0); } - if (entity.assigneeLastName) { - initials += entity.assigneeLastName.charAt(0); + if (entity.assignee.lastName) { + initials += entity.assignee.lastName.charAt(0); } } else { - initials += entity.assigneeEmail.charAt(0); + initials += entity.assignee.email.charAt(0); } return initials.toUpperCase(); } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss index b6dcfdecea..a7a408b419 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss @@ -22,6 +22,10 @@ justify-content: flex-start; align-items: center; .assigned-container { + max-width: 180px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; .user-avatar { display: inline-flex; justify-content: center; @@ -30,6 +34,8 @@ border-radius: 50%; width: 28px; height: 28px; + min-width: 28px; + min-height: 28px; color: white; font-size: 13px; font-weight: 700; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html index def95c93f9..694a96047e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html @@ -84,20 +84,23 @@ - + {{ getUserInitials(alarm) }} - {{ getUserDisplayName(alarm) }} + {{ getUserDisplayName(alarm) }} account_circle alarm.unassigned - +
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss new file mode 100644 index 0000000000..5736aa8ef1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host { + .tb-assignee { + cursor: pointer; + + .assigned-container { + max-width: 233px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + .user-avatar { + display: inline-flex; + justify-content: center; + align-items: center; + margin-right: 8px; + border-radius: 50%; + width: 28px; + height: 28px; + min-width: 28px; + min-height: 28px; + color: white; + font-size: 13px; + font-weight: 700; + } + } + .material-icons.unassigned-icon { + width: 28px; + height: 28px; + font-size: 28px; + margin-right: 8px; + color: rgba(0, 0, 0, 0.38); + overflow: visible; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts new file mode 100644 index 0000000000..5d7f29d55b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts @@ -0,0 +1,141 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + Component, EventEmitter, Injector, Input, Output, StaticProvider, ViewContainerRef +} from '@angular/core'; +import { UtilsService } from '@core/services/utils.service'; +import { AlarmAssignee, AlarmInfo } from '@shared/models/alarm.models'; +import { + ALARM_ASSIGNEE_PANEL_DATA, AlarmAssigneePanelComponent, + AlarmAssigneePanelData +} from '@home/components/alarm/alarm-assignee-panel.component'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; + +@Component({ + selector: 'tb-alarm-assignee', + templateUrl: './alarm-assignee.component.html', + styleUrls: ['./alarm-assignee.component.scss'] +}) +export class AlarmAssigneeComponent { + @Input() + alarm: AlarmInfo; + + @Output() + alarmReassigned = new EventEmitter(); + + constructor(private utilsService: UtilsService, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef) { + } + + getUserDisplayName(entity: AlarmAssignee) { + let displayName = ''; + if ((entity.firstName && entity.firstName.length > 0) || + (entity.lastName && entity.lastName.length > 0)) { + if (entity.firstName) { + displayName += entity.firstName; + } + if (entity.lastName) { + if (displayName.length > 0) { + displayName += ' '; + } + displayName += entity.lastName; + } + } else { + displayName = entity.email; + } + return displayName; + } + + getUserInitials(entity: AlarmAssignee): string { + let initials = ''; + if (entity.firstName && entity.firstName.length || + entity.lastName && entity.lastName.length) { + if (entity.firstName) { + initials += entity.firstName.charAt(0); + } + if (entity.lastName) { + initials += entity.lastName.charAt(0); + } + } else { + initials += entity.email.charAt(0); + } + return initials.toUpperCase(); + } + + getFullName(entity: AlarmAssignee): string { + let fullName = ''; + if ((entity.firstName && entity.firstName.length > 0) || + (entity.lastName && entity.lastName.length > 0)) { + if (entity.firstName) { + fullName += entity.firstName; + } + if (entity.lastName) { + if (fullName.length > 0) { + fullName += ' '; + } + fullName += entity.lastName; + } + } + return fullName; + } + + getAvatarBgColor(entity: AlarmAssignee) { + return this.utilsService.stringToHslColor(this.getUserDisplayName(entity), 40, 60); + } + + openAlarmAssigneePanel($event: Event, alarm: AlarmInfo) { + if ($event) { + $event.stopPropagation(); + } + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + config.minWidth = '260px'; + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + const providers: StaticProvider[] = [ + { + provide: ALARM_ASSIGNEE_PANEL_DATA, + useValue: { + alarmId: alarm.id.id, + assigneeId: alarm.assigneeId?.id + } as AlarmAssigneePanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); + overlayRef.attach(new ComponentPortal(AlarmAssigneePanelComponent, + this.viewContainerRef, injector)).onDestroy(() => this.alarmReassigned.emit(true)); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html index c8bbbe5537..c1be93431d 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html @@ -56,14 +56,10 @@ alarm.status - - alarm.assignee - - {{ assigneeInitials }} - - - + +
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts index a8d24bdafa..e86b470566 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts @@ -34,7 +34,6 @@ import { tap } from 'rxjs/operators'; import { DatePipe } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; -import { UtilsService } from '@core/services/utils.service'; export interface AlarmDetailsDialogData { alarmId?: string; @@ -70,8 +69,6 @@ export class AlarmDetailsDialogComponent extends DialogComponent, protected router: Router, private datePipe: DatePipe, @@ -79,8 +76,7 @@ export class AlarmDetailsDialogComponent extends DialogComponent, - public fb: UntypedFormBuilder, - private utilsService: UtilsService) { + public fb: UntypedFormBuilder) { super(store, router, dialogRef); this.allowAcknowledgment = data.allowAcknowledgment; @@ -91,15 +87,12 @@ export class AlarmDetailsDialogComponent extends DialogComponent 0) || - (entity.assignee.lastName && entity.assignee.lastName.length > 0)) { - if (entity.assignee.firstName) { - displayName += entity.assignee.firstName; - } - if (entity.assignee.lastName) { - if (displayName.length > 0) { - displayName += ' '; - } - displayName += entity.assignee.lastName; - } - } else { - displayName = entity.assignee.email; - } - return displayName; - } - - getUserInitials(entity: AlarmInfo): string { - let initials = ''; - if (entity.assignee.firstName && entity.assignee.firstName.length || - entity.assignee.lastName && entity.assignee.lastName.length) { - if (entity.assignee.firstName) { - initials += entity.assignee.firstName.charAt(0); - } - if (entity.assignee.lastName) { - initials += entity.assignee.lastName.charAt(0); - } - } else { - initials += entity.assignee.email.charAt(0); - } - return initials.toUpperCase(); + onReassign(): void { + this.alarmUpdated = true; + this.loadAlarm() + this.alarmCommentComponent.loadAlarmComments(); } - } diff --git a/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts b/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts index fbfc297439..865e7cf224 100644 --- a/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts @@ -21,6 +21,7 @@ import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-detail import { SHARED_HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens'; import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; import { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-comment-dialog.component'; +import { AlarmAssigneeComponent } from '@home/components/alarm/alarm-assignee.component'; @NgModule({ providers: [ @@ -30,7 +31,8 @@ import { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-commen [ AlarmDetailsDialogComponent, AlarmCommentComponent, - AlarmCommentDialogComponent + AlarmCommentDialogComponent, + AlarmAssigneeComponent ], imports: [ CommonModule, @@ -39,7 +41,8 @@ import { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-commen exports: [ AlarmDetailsDialogComponent, AlarmCommentComponent, - AlarmCommentDialogComponent + AlarmCommentDialogComponent, + AlarmAssigneeComponent ] }) export class SharedHomeComponentsModule { } From b1e33b4be02ffa92ee7830f4de052084343d7419 Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 27 Feb 2023 15:56:14 +0200 Subject: [PATCH 51/60] Added assignee coulumn and comments action button to load demo and default alarm table widgets --- .../main/data/json/demo/dashboards/thermostats.json | 10 ++++++++++ .../data/json/system/widget_bundles/alarm_widgets.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/application/src/main/data/json/demo/dashboards/thermostats.json b/application/src/main/data/json/demo/dashboards/thermostats.json index 4f486effdf..c014d8875a 100644 --- a/application/src/main/data/json/demo/dashboards/thermostats.json +++ b/application/src/main/data/json/demo/dashboards/thermostats.json @@ -215,6 +215,8 @@ "displayDetails": true, "allowAcknowledgment": true, "allowClear": true, + "allowAssign": true, + "displayComments": true, "displayPagination": true, "defaultPageSize": 10, "defaultSortOrder": "-createdTime", @@ -277,6 +279,14 @@ "color": "#607d8b", "settings": {}, "_hash": 0.7977920750136249 + }, + { + "name": "assignee", + "type": "alarm", + "label": "Assignee", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.8678751039018493 } ] }, diff --git a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json index 448fcaf52c..e7a97b36f8 100644 --- a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json @@ -23,7 +23,7 @@ "dataKeySettingsSchema": "", "settingsDirective": "tb-alarms-table-widget-settings", "dataKeySettingsDirective": "tb-alarms-table-key-settings", - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false}" + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"allowAssign\":true,\"displayComments\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418},{\"name\":\"assignee\",\"type\":\"alarm\",\"label\":\"Assignee\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.5008441077416634}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false}" } } ] From 6c160f593051ca1a23547c5976c61f21da36d6ac Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 28 Feb 2023 11:37:47 +0200 Subject: [PATCH 52/60] Backward compatibility of the alarm save call --- .../server/controller/AlarmController.java | 14 ++--- .../entitiy/alarm/DefaultTbAlarmService.java | 45 +++++++++++++--- .../service/entitiy/alarm/TbAlarmService.java | 8 ++- .../controller/BaseAlarmControllerTest.java | 54 ++++++++++++++++++- 4 files changed, 103 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 196a660e00..78ee6620fe 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -119,7 +119,7 @@ public class AlarmController extends BaseController { return checkAlarmInfoId(alarmId, Operation.READ); } - @ApiOperation(value = "Create or update Alarm (saveAlarm)", + @ApiOperation(value = "Create or Update Alarm (saveAlarm)", notes = "Creates or Updates the Alarm. " + "When creating alarm, platform generates Alarm Id as " + UUID_WIKI_LINK + "The newly created Alarm id will be present in the response. Specify existing Alarm id to update the alarm. " + @@ -193,9 +193,9 @@ public class AlarmController extends BaseController { @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 + @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); @@ -203,7 +203,7 @@ public class AlarmController extends BaseController { Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); UserId assigneeId = new UserId(UUID.fromString(strAssigneeId)); checkUserId(assigneeId, Operation.READ); - return tbAlarmService.assign(alarm, getCurrentUser(), assigneeId); + return tbAlarmService.assign(alarm, assigneeId, System.currentTimeMillis(), getCurrentUser()); } @ApiOperation(value = "Unassign Alarm (unassignAlarm)", @@ -214,12 +214,12 @@ public class AlarmController extends BaseController { @RequestMapping(value = "/alarm/{alarmId}/assign", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public Alarm unassignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) - @PathVariable(ALARM_ID) String strAlarmId + @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()); + return tbAlarmService.unassign(alarm, System.currentTimeMillis(), getCurrentUser()); } @ApiOperation(value = "Get Alarms (getAlarms)", diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index 012a5b0b8f..652529caa1 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -53,11 +53,28 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } else { result = alarmSubscriptionService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm)); } + if (!result.isSuccessful()) { + throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); + } actionType = result.isCreated() ? ActionType.ADDED : ActionType.UPDATED; if (result.isModified()) { notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), actionType, user); } - return new Alarm(result.getAlarm()); + AlarmInfo resultAlarm = result.getAlarm(); + if (alarm.isAcknowledged() && !resultAlarm.isAcknowledged()) { + resultAlarm = ack(resultAlarm, alarm.getAckTs(), user); + } + if (alarm.isCleared() && !resultAlarm.isCleared()) { + resultAlarm = clear(resultAlarm, alarm.getClearTs(), user); + } + UserId newAssignee = alarm.getAssigneeId(); + UserId curAssignee = resultAlarm.getAssigneeId(); + if (newAssignee != null && !newAssignee.equals(curAssignee)) { + resultAlarm = assign(alarm, newAssignee, alarm.getAssignTs(), user); + } else if (newAssignee == null && curAssignee != null) { + resultAlarm = unassign(alarm, alarm.getAssignTs(), user); + } + return new Alarm(resultAlarm); } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ALARM), alarm, actionType, user, e); throw e; @@ -66,7 +83,12 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb @Override public AlarmInfo ack(Alarm alarm, User user) throws ThingsboardException { - AlarmApiCallResult result = alarmSubscriptionService.acknowledgeAlarm(alarm.getTenantId(), alarm.getId(), System.currentTimeMillis()); + return ack(alarm, System.currentTimeMillis(), user); + } + + @Override + public AlarmInfo ack(Alarm alarm, long ackTs, User user) throws ThingsboardException { + AlarmApiCallResult result = alarmSubscriptionService.acknowledgeAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(ackTs)); if (!result.isSuccessful()) { throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); } @@ -89,7 +111,12 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb @Override public AlarmInfo clear(Alarm alarm, User user) throws ThingsboardException { - AlarmApiCallResult result = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), System.currentTimeMillis(), null); + return clear(alarm, System.currentTimeMillis(), user); + } + + @Override + public AlarmInfo clear(Alarm alarm, long clearTs, User user) throws ThingsboardException { + AlarmApiCallResult result = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(clearTs), null); if (!result.isSuccessful()) { throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); } @@ -111,8 +138,8 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } @Override - public AlarmInfo assign(Alarm alarm, User user, UserId assigneeId) throws ThingsboardException { - AlarmApiCallResult result = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, System.currentTimeMillis()); + public AlarmInfo assign(Alarm alarm, UserId assigneeId, long assignTs, User user) throws ThingsboardException { + AlarmApiCallResult result = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, getOrDefault(assignTs)); if (!result.isSuccessful()) { throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); } @@ -138,8 +165,8 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } @Override - public AlarmInfo unassign(Alarm alarm, User user) throws ThingsboardException { - AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), System.currentTimeMillis()); + public AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException { + AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), getOrDefault(unassignTs)); if (!result.isSuccessful()) { throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); } @@ -169,4 +196,8 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb relatedEdgeIds, user, JacksonUtil.toString(alarm)); return alarmSubscriptionService.deleteAlarm(tenantId, alarm.getId()); } + + private static long getOrDefault(long ts) { + return ts > 0 ? ts : System.currentTimeMillis(); + } } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java index e4b40f1305..a2ae9c8cc7 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java @@ -27,11 +27,15 @@ public interface TbAlarmService { AlarmInfo ack(Alarm alarm, User user) throws ThingsboardException; + AlarmInfo ack(Alarm alarm, long ackTs, User user) throws ThingsboardException; + AlarmInfo clear(Alarm alarm, User user) throws ThingsboardException; - AlarmInfo assign(Alarm alarm, User user, UserId assigneeId) throws ThingsboardException; + AlarmInfo clear(Alarm alarm, long clearTs, User user) throws ThingsboardException; + + AlarmInfo assign(Alarm alarm, UserId assigneeId, long assignTs, User user) throws ThingsboardException; - AlarmInfo unassign(Alarm alarm, User user) throws ThingsboardException; + AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException; Boolean delete(Alarm alarm, User user); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java index b7234f1008..777b97d940 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java @@ -145,6 +145,54 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { AlarmInfo foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class); testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UPDATED); + + alarm = updatedAlarm; + alarm.setAcknowledged(true); + alarm.setAckTs(System.currentTimeMillis() - 1000); + updatedAlarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertNotNull(updatedAlarm); + Assert.assertTrue(updatedAlarm.isAcknowledged()); + Assert.assertEquals(alarm.getAckTs(), updatedAlarm.getAckTs()); + + foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class); + testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), + tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ACK); + + alarm = updatedAlarm; + alarm.setCleared(true); + alarm.setClearTs(System.currentTimeMillis() - 1000); + updatedAlarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertNotNull(updatedAlarm); + Assert.assertTrue(updatedAlarm.isCleared()); + Assert.assertEquals(alarm.getClearTs(), updatedAlarm.getClearTs()); + + foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class); + testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), + tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_CLEAR); + + alarm = updatedAlarm; + alarm.setAssigneeId(tenantAdminUserId); + alarm.setAssignTs(System.currentTimeMillis() - 1000); + updatedAlarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertNotNull(updatedAlarm); + Assert.assertEquals(tenantAdminUserId, updatedAlarm.getAssigneeId()); + Assert.assertEquals(alarm.getAssignTs(), updatedAlarm.getAssignTs()); + + foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class); + testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), + tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN); + + alarm = updatedAlarm; + alarm.setAssigneeId(null); + alarm.setAssignTs(System.currentTimeMillis() - 1000); + updatedAlarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertNotNull(updatedAlarm); + Assert.assertNull(updatedAlarm.getAssigneeId()); + Assert.assertEquals(alarm.getAssignTs(), updatedAlarm.getAssignTs()); + + foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class); + testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), + tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGN); } @Test @@ -490,7 +538,8 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { var response = doGetTyped( "/api/alarm/" + EntityType.DEVICE + "/" + customerDevice.getUuidId() + "?page=0&pageSize=" + size, - new TypeReference>() {} + new TypeReference>() { + } ); var foundAlarmInfos = response.getData(); Assert.assertNotNull("Found pageData is null", foundAlarmInfos); @@ -556,7 +605,8 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { this.token = tokens.get("token").asText(); PageData pageData = doGetTyped( - "/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=1", new TypeReference>() {} + "/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=1", new TypeReference>() { + } ); Assert.assertNotNull("Found pageData is null", pageData); From b9cf5c78361ed016353c2e74c05d3b8d8fdf3c4f Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 28 Feb 2023 12:03:59 +0200 Subject: [PATCH 53/60] Improvement of the save alarm procedure --- .../entitiy/alarm/DefaultTbAlarmService.java | 4 ++-- .../DefaultAlarmSubscriptionService.java | 17 ++++++++++++----- .../alarm/AlarmCreateOrUpdateActiveRequest.java | 8 ++++++++ .../data/alarm/AlarmModificationRequest.java | 3 +++ .../common/data/alarm/AlarmUpdateRequest.java | 8 ++++++++ 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index 652529caa1..e452a766a4 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -49,9 +49,9 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb try { AlarmApiCallResult result; if (alarm.getId() == null) { - result = alarmSubscriptionService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm)); + result = alarmSubscriptionService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, user.getId())); } else { - result = alarmSubscriptionService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm)); + result = alarmSubscriptionService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm, user.getId())); } if (!result.isSuccessful()) { throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 48f8760503..6eb5f16b27 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmModificationRequest; import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; @@ -103,7 +104,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService if (result.isCreated()) { apiUsageClient.report(request.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT); } - return withWsCallback(result); + return withWsCallback(request, result); } @Override @@ -298,17 +299,23 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService } private AlarmApiCallResult withWsCallback(AlarmApiCallResult result) { + return withWsCallback(null, result); + } + + private AlarmApiCallResult withWsCallback(AlarmModificationRequest request, AlarmApiCallResult result) { if (result.isSuccessful() && result.isModified()) { Futures.addCallback(Futures.immediateFuture(result), new AlarmUpdateCallback(), wsCallBackExecutor); if (result.isSeverityChanged()) { AlarmInfo alarm = result.getAlarm(); - AlarmComment alarmComment = AlarmComment.builder() + AlarmComment.AlarmCommentBuilder alarmComment = AlarmComment.builder() .alarmId(alarm.getId()) .type(AlarmCommentType.SYSTEM) .comment(JacksonUtil.newObjectNode().put("text", - String.format("Alarm severity was updated from %s to %s", result.getOldSeverity(), alarm.getSeverity()))) - .build(); - alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment); + String.format("Alarm severity was updated from %s to %s", result.getOldSeverity(), alarm.getSeverity()))); + if (request != null && request.getUserId() != null) { + alarmComment.userId(request.getUserId()); + } + alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment.build()); } } return result; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java index d599f9cb0d..ecf882e2c9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java @@ -22,6 +22,7 @@ import lombok.Data; 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; @@ -58,7 +59,13 @@ public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationReques @ApiModelProperty(position = 9, value = "JSON object with propagation details") private AlarmPropagationInfo propagation; + private UserId userId; + public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a) { + return fromAlarm(a, null); + } + + public static AlarmCreateOrUpdateActiveRequest fromAlarm(Alarm a, UserId userId) { return AlarmCreateOrUpdateActiveRequest.builder() .tenantId(a.getTenantId()) .customerId(a.getCustomerId()) @@ -73,6 +80,7 @@ public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationReques .propagateToOwner(a.isPropagateToOwner()) .propagateToTenant(a.isPropagateToTenant()) .propagateRelationTypes(a.getPropagateRelationTypes()).build()) + .userId(userId) .build(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.java index 27c31854ef..e40cb15d22 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.alarm; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; public interface AlarmModificationRequest { @@ -28,4 +29,6 @@ public interface AlarmModificationRequest { void setStartTs(long startTs); void setEndTs(long endTs); + + UserId getUserId(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java index f284bb16d6..23edafd6d2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java @@ -21,6 +21,7 @@ import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.Valid; @@ -53,7 +54,13 @@ public class AlarmUpdateRequest implements AlarmModificationRequest { @ApiModelProperty(position = 7, value = "JSON object with propagation details") private AlarmPropagationInfo propagation; + private UserId userId; + public static AlarmUpdateRequest fromAlarm(Alarm a) { + return fromAlarm(a, null); + } + + public static AlarmUpdateRequest fromAlarm(Alarm a, UserId userId) { return AlarmUpdateRequest.builder() .tenantId(a.getTenantId()) .alarmId(a.getId()) @@ -66,6 +73,7 @@ public class AlarmUpdateRequest implements AlarmModificationRequest { .propagateToOwner(a.isPropagateToOwner()) .propagateToTenant(a.isPropagateToTenant()) .propagateRelationTypes(a.getPropagateRelationTypes()).build()) + .userId(userId) .build(); } } From 69aafef56837a204eb58af1b197a79d0fe560814 Mon Sep 17 00:00:00 2001 From: rusikv Date: Tue, 28 Feb 2023 15:04:03 +0200 Subject: [PATCH 54/60] Changed display comments button position, moved comments dialog actions on top, added ignore loading on writing comments and reassign --- .../alarm/alarm-assignee-panel.component.ts | 4 +-- .../alarm/alarm-comment-dialog.component.html | 15 ++--------- .../alarm/alarm-comment-dialog.component.ts | 14 ++-------- .../alarm/alarm-comment.component.html | 10 ++++--- .../alarm/alarm-comment.component.ts | 12 ++++----- .../alarm/alarm-details-dialog.component.ts | 2 +- .../lib/alarms-table-widget.component.html | 4 ++- .../lib/alarms-table-widget.component.ts | 26 +++++++++---------- ...larms-table-widget-settings.component.html | 6 ++--- 9 files changed, 38 insertions(+), 55 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts index 2cf382afcf..bd50beef31 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts @@ -129,12 +129,12 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe } assign(user: User): void { - this.alarmService.assignAlarm(this.alarmId, user.id.id).subscribe( + this.alarmService.assignAlarm(this.alarmId, user.id.id, {ignoreLoading: true}).subscribe( () => this.overlayRef.dispose()); } unassign(): void { - this.alarmService.unassignAlarm(this.alarmId).subscribe( + this.alarmService.unassignAlarm(this.alarmId, {ignoreLoading: true}).subscribe( () => this.overlayRef.dispose()); } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html index 76fac3aaae..94f8f47eb1 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html @@ -33,23 +33,12 @@ [commentsHeaderEnabled]="commentsHeaderEnabled">
-
- - - -
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts index 5f28ce905e..026ebae8c0 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts @@ -14,14 +14,13 @@ /// limitations under the License. /// -import { Component, Inject, ViewChild } from '@angular/core'; +import { Component, Inject } from '@angular/core'; import { DialogComponent } from '@shared/components/dialog.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Router } from '@angular/router'; import { AlarmInfo } from '@shared/models/alarm.models'; -import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; export interface AlarmCommentDialogData { alarmId?: string; @@ -37,9 +36,8 @@ export interface AlarmCommentDialogData { export class AlarmCommentDialogComponent extends DialogComponent { alarmId: string; - commentsHeaderEnabled: boolean = false; - @ViewChild('alarmCommentComponent', { static: true }) alarmCommentComponent: AlarmCommentComponent; + commentsHeaderEnabled: boolean = false; constructor(protected store: Store, protected router: Router, @@ -50,14 +48,6 @@ export class AlarmCommentDialogComponent extends DialogComponent -
-
- {{ 'alarm-comment.comments' | translate }} -
+
+
+ + {{ 'alarm-comment.comments' | translate }} + +
-
- + -