diff --git a/application/src/main/data/upgrade/3.6.3/schema_update.sql b/application/src/main/data/upgrade/3.6.3/schema_update.sql new file mode 100644 index 0000000000..3e0fdc3b4b --- /dev/null +++ b/application/src/main/data/upgrade/3.6.3/schema_update.sql @@ -0,0 +1,27 @@ +-- +-- Copyright © 2016-2024 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. +-- + +-- NOTIFICATIONS UPDATE START + +ALTER TABLE notification ADD COLUMN IF NOT EXISTS delivery_method VARCHAR(50) NOT NULL default 'WEB'; + +DROP INDEX IF EXISTS idx_notification_recipient_id_created_time; +DROP INDEX IF EXISTS idx_notification_recipient_id_unread; + +CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_created_time ON notification(delivery_method, recipient_id, created_time DESC); +CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_unread ON notification(delivery_method, recipient_id) WHERE status <> 'READ'; + +-- NOTIFICATIONS UPDATE END diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index d2b095c7fa..459d3292f8 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -105,6 +105,8 @@ public class NotificationController extends BaseController { private final NotificationCenter notificationCenter; private final NotificationSettingsService notificationSettingsService; + private static final String DELIVERY_METHOD_ALLOWABLE_VALUES = "WEB,MOBILE_APP"; + @ApiOperation(value = "Get notifications (getNotifications)", notes = "Returns the page of notifications for current user." + NEW_LINE + PAGE_DATA_PARAMETERS + @@ -172,10 +174,23 @@ public class NotificationController extends BaseController { @RequestParam(required = false) String sortOrder, @ApiParam(value = "To search for unread notifications only") @RequestParam(defaultValue = "false") boolean unreadOnly, + @ApiParam(value = "Delivery method", allowableValues = DELIVERY_METHOD_ALLOWABLE_VALUES) + @RequestParam(defaultValue = "WEB") NotificationDeliveryMethod deliveryMethod, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { // no permissions PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return notificationService.findNotificationsByRecipientIdAndReadStatus(user.getTenantId(), user.getId(), unreadOnly, pageLink); + return notificationService.findNotificationsByRecipientIdAndReadStatus(user.getTenantId(), deliveryMethod, user.getId(), unreadOnly, pageLink); + } + + @ApiOperation(value = "Get unread notifications count (getUnreadNotificationsCount)", + notes = "Returns unread notifications count for chosen delivery method." + + AVAILABLE_FOR_ANY_AUTHORIZED_USER) + @GetMapping("/notifications/unread/count") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public Integer getUnreadNotificationsCount(@ApiParam(value = "Delivery method", allowableValues = DELIVERY_METHOD_ALLOWABLE_VALUES) + @RequestParam(defaultValue = "MOBILE_APP") NotificationDeliveryMethod deliveryMethod, + @AuthenticationPrincipal SecurityUser user) { + return notificationService.countUnreadNotificationsByRecipientId(user.getTenantId(), deliveryMethod, user.getId()); } @ApiOperation(value = "Mark notification as read (markNotificationAsRead)", @@ -195,9 +210,11 @@ public class NotificationController extends BaseController { AVAILABLE_FOR_ANY_AUTHORIZED_USER) @PutMapping("/notifications/read") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - public void markAllNotificationsAsRead(@AuthenticationPrincipal SecurityUser user) { + public void markAllNotificationsAsRead(@ApiParam(value = "Delivery method", allowableValues = DELIVERY_METHOD_ALLOWABLE_VALUES) + @RequestParam(defaultValue = "WEB") NotificationDeliveryMethod deliveryMethod, + @AuthenticationPrincipal SecurityUser user) { // no permissions - notificationCenter.markAllNotificationsAsRead(user.getTenantId(), user.getId()); + notificationCenter.markAllNotificationsAsRead(user.getTenantId(), deliveryMethod, user.getId()); } @ApiOperation(value = "Delete notification (deleteNotification)", diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java index 26084ad077..513aff6891 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java @@ -33,13 +33,13 @@ public class ThingsboardErrorResponse { // Error code private final ThingsboardErrorCode errorCode; - private final Date timestamp; + private final long timestamp; protected ThingsboardErrorResponse(final String message, final ThingsboardErrorCode errorCode, HttpStatus status) { this.message = message; this.errorCode = errorCode; this.status = status; - this.timestamp = new java.util.Date(); + this.timestamp = System.currentTimeMillis(); } public static ThingsboardErrorResponse of(final String message, final ThingsboardErrorCode errorCode, HttpStatus status) { @@ -75,7 +75,7 @@ public class ThingsboardErrorResponse { } @ApiModelProperty(position = 4, value = "Timestamp", accessMode = ApiModelProperty.AccessMode.READ_ONLY) - public Date getTimestamp() { + public long getTimestamp() { return timestamp; } } diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java index b2509187d5..2e572ce8c4 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java @@ -16,6 +16,8 @@ package org.thingsboard.server.exception; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -28,7 +30,9 @@ import org.springframework.security.authentication.LockedException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.context.request.WebRequest; @@ -43,16 +47,21 @@ import org.thingsboard.server.service.security.exception.JwtExpiredTokenExceptio import org.thingsboard.server.service.security.exception.UserPasswordExpiredException; import org.thingsboard.server.service.security.exception.UserPasswordNotValidException; +import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.Optional; + +import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION; @Slf4j +@Controller @RestControllerAdvice -public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHandler implements AccessDeniedHandler { +public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHandler implements AccessDeniedHandler, ErrorController { private static final Map statusToErrorCodeMap = new HashMap<>(); static { @@ -90,6 +99,17 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand return errorCodeToStatusMap.getOrDefault(errorCode, HttpStatus.INTERNAL_SERVER_ERROR); } + @RequestMapping("/error") + public ResponseEntity handleError(HttpServletRequest request) { + HttpStatus httpStatus = Optional.ofNullable(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)) + .map(status -> HttpStatus.resolve(Integer.parseInt(status.toString()))) + .orElse(HttpStatus.INTERNAL_SERVER_ERROR); + String errorMessage = Optional.ofNullable(request.getAttribute(ERROR_EXCEPTION)) + .map(e -> (ExceptionUtils.getMessage((Throwable) e))) + .orElse(httpStatus.getReasonPhrase()); + return new ResponseEntity<>(ThingsboardErrorResponse.of(errorMessage, statusToErrorCode(httpStatus), httpStatus), httpStatus); + } + @Override @ExceptionHandler(AccessDeniedException.class) public void handle(HttpServletRequest request, HttpServletResponse response, diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java index 6b72e49df6..f75bbd219e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java @@ -78,6 +78,24 @@ public class TenantMsgConstructorV1 implements TenantMsgConstructor { @Override public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) { + tenantProfile = JacksonUtil.clone(tenantProfile); + // clear all config + var tenantProfileData = tenantProfile.getProfileData(); + var configuration = tenantProfile.getDefaultProfileConfiguration(); + configuration.setRpcTtlDays(0); + configuration.setMaxJSExecutions(0); + configuration.setMaxREExecutions(0); + configuration.setMaxDPStorageDays(0); + configuration.setMaxTbelExecutions(0); + configuration.setQueueStatsTtlDays(0); + configuration.setMaxTransportMessages(0); + configuration.setDefaultStorageTtlDays(0); + configuration.setMaxTransportDataPoints(0); + configuration.setRuleEngineExceptionsTtlDays(0); + configuration.setMaxRuleNodeExecutionsPerMessage(0); + tenantProfileData.setConfiguration(configuration); + tenantProfile.setProfileData(tenantProfileData); + ByteString profileData = EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_2) ? ByteString.empty() : ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile.getProfileData())); TenantProfileUpdateMsg.Builder builder = TenantProfileUpdateMsg.newBuilder() @@ -93,4 +111,5 @@ public class TenantMsgConstructorV1 implements TenantMsgConstructor { } return builder.build(); } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java index c6ccb439ea..274e18e7f3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java @@ -19,6 +19,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; @@ -36,6 +37,23 @@ public class TenantMsgConstructorV2 implements TenantMsgConstructor { @Override public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) { + tenantProfile = JacksonUtil.clone(tenantProfile); + // clear all config + var configuration = tenantProfile.getDefaultProfileConfiguration(); + configuration.setRpcTtlDays(0); + configuration.setMaxJSExecutions(0); + configuration.setMaxREExecutions(0); + configuration.setMaxDPStorageDays(0); + configuration.setMaxTbelExecutions(0); + configuration.setQueueStatsTtlDays(0); + configuration.setMaxTransportMessages(0); + configuration.setDefaultStorageTtlDays(0); + configuration.setMaxTransportDataPoints(0); + configuration.setRuleEngineExceptionsTtlDays(0); + configuration.setMaxRuleNodeExecutionsPerMessage(0); + tenantProfile.getProfileData().setConfiguration(configuration); + return TenantProfileUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(tenantProfile)).build(); } + } 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 ff8dd52ec5..6cbdbdc98a 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 @@ -25,10 +25,9 @@ import org.springframework.context.annotation.Lazy; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.alarm.AlarmInfo; -import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; 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.TenantId; @@ -72,11 +71,11 @@ public abstract class AbstractTbEntityService { @Lazy private EntitiesVersionControlService vcService; - protected void removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) { - PageData alarms = - alarmService.findAlarms(tenantId, new AlarmQuery(entityId, new TimePageLink(Integer.MAX_VALUE), null, null, null, false)); + protected void removeAlarmsByOriginatorId(TenantId tenantId, EntityId entityId) { + PageData alarms = + alarmService.findAlarmIdsByOriginatorId(tenantId, entityId, new TimePageLink(Integer.MAX_VALUE)); - alarms.getData().stream().map(AlarmInfo::getId).forEach(alarmId -> alarmService.delAlarm(tenantId, alarmId)); + alarms.getData().forEach(alarmId -> alarmService.delAlarm(tenantId, alarmId)); } protected T checkNotNull(T reference) throws ThingsboardException { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java index 58940318be..860f4e63ce 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java @@ -78,7 +78,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb TenantId tenantId = asset.getTenantId(); AssetId assetId = asset.getId(); try { - removeAlarmsByEntityId(tenantId, assetId); + removeAlarmsByOriginatorId(tenantId, assetId); assetService.deleteAsset(tenantId, assetId); notificationEntityService.logEntityAction(tenantId, assetId, asset, asset.getCustomerId(), actionType, user, assetId.toString()); tbClusterService.broadcastEntityStateChangeEvent(tenantId, assetId, ComponentLifecycleEvent.DELETED); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java index 8d43a43cbc..a56567388e 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java @@ -97,7 +97,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); try { - removeAlarmsByEntityId(tenantId, deviceId); + removeAlarmsByOriginatorId(tenantId, deviceId); deviceService.deleteDevice(tenantId, deviceId); notificationEntityService.notifyDeleteDevice(tenantId, deviceId, device.getCustomerId(), device, user, deviceId.toString()); diff --git a/application/src/main/java/org/thingsboard/server/service/mail/RefreshTokenExpCheckService.java b/application/src/main/java/org/thingsboard/server/service/mail/RefreshTokenExpCheckService.java index 32fe4eb15c..7c7edf2164 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/RefreshTokenExpCheckService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/RefreshTokenExpCheckService.java @@ -54,23 +54,35 @@ public class RefreshTokenExpCheckService { AdminSettings settings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"); if (settings != null && settings.getJsonValue().has("enableOauth2") && settings.getJsonValue().get("enableOauth2").asBoolean()) { JsonNode jsonValue = settings.getJsonValue(); - if (OFFICE_365.name().equals(jsonValue.get("providerId").asText()) && jsonValue.has("refreshTokenExpires")) { - long expiresIn = jsonValue.get("refreshTokenExpires").longValue(); - if ((expiresIn - System.currentTimeMillis()) < 604800000L) { //less than 7 days - log.info("Trying to refresh refresh token."); + if (OFFICE_365.name().equals(jsonValue.get("providerId").asText()) && jsonValue.has("refreshToken") + && jsonValue.has("refreshTokenExpires")) { + try { + long expiresIn = jsonValue.get("refreshTokenExpires").longValue(); + long tokenLifeDuration = expiresIn - System.currentTimeMillis(); + if (tokenLifeDuration < 0) { + ((ObjectNode) jsonValue).put("tokenGenerated", false); + ((ObjectNode) jsonValue).remove("refreshToken"); + ((ObjectNode) jsonValue).remove("refreshTokenExpires"); - String clientId = jsonValue.get("clientId").asText(); - String clientSecret = jsonValue.get("clientSecret").asText(); - String refreshToken = jsonValue.get("refreshToken").asText(); - String tokenUri = jsonValue.get("tokenUri").asText(); + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, settings); + } else if (tokenLifeDuration < 604800000L) { //less than 7 days + log.info("Trying to refresh refresh token."); - TokenResponse tokenResponse = new RefreshTokenRequest(new NetHttpTransport(), new GsonFactory(), - new GenericUrl(tokenUri), refreshToken) - .setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret)) - .execute(); - ((ObjectNode) jsonValue).put("refreshToken", tokenResponse.getRefreshToken()); - ((ObjectNode) jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli()); - adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, settings); + String clientId = jsonValue.get("clientId").asText(); + String clientSecret = jsonValue.get("clientSecret").asText(); + String refreshToken = jsonValue.get("refreshToken").asText(); + String tokenUri = jsonValue.get("tokenUri").asText(); + + TokenResponse tokenResponse = new RefreshTokenRequest(new NetHttpTransport(), new GsonFactory(), + new GenericUrl(tokenUri), refreshToken) + .setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret)) + .execute(); + ((ObjectNode) jsonValue).put("refreshToken", tokenResponse.getRefreshToken()); + ((ObjectNode) jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli()); + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, settings); + } + } catch (Exception e) { + log.error("Error occurred while checking token", e); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index 25a344b775..2ab7692dbc 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -82,6 +82,8 @@ import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.WEB; + @Service @Slf4j @RequiredArgsConstructor @@ -192,7 +194,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple NotificationProcessingContext ctx = NotificationProcessingContext.builder() .tenantId(tenantId) .request(notificationRequest) - .deliveryMethods(Set.of(NotificationDeliveryMethod.WEB)) + .deliveryMethods(Set.of(WEB)) .template(template) .build(); @@ -323,6 +325,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple .requestId(request.getId()) .recipientId(recipient.getId()) .type(ctx.getNotificationType()) + .deliveryMethod(WEB) .subject(processedTemplate.getSubject()) .text(processedTemplate.getBody()) .additionalConfig(processedTemplate.getAdditionalConfig()) @@ -348,19 +351,22 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId); if (updated) { log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId); - NotificationUpdate update = NotificationUpdate.builder() - .updated(true) - .notificationId(notificationId.getId()) - .newStatus(NotificationStatus.READ) - .build(); - onNotificationUpdate(tenantId, recipientId, update); + Notification notification = notificationService.findNotificationById(tenantId, notificationId); + if (notification.getDeliveryMethod() == WEB) { + NotificationUpdate update = NotificationUpdate.builder() + .updated(true) + .notificationId(notificationId.getId()) + .newStatus(NotificationStatus.READ) + .build(); + onNotificationUpdate(tenantId, recipientId, update); + } } } @Override - public void markAllNotificationsAsRead(TenantId tenantId, UserId recipientId) { - int updatedCount = notificationService.markAllNotificationsAsRead(tenantId, recipientId); - if (updatedCount > 0) { + public void markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) { + int updatedCount = notificationService.markAllNotificationsAsRead(tenantId, deliveryMethod, recipientId); + if (updatedCount > 0 && deliveryMethod == WEB) { log.trace("Marked all notifications as read (recipient id: {}, tenant id: {})", recipientId, tenantId); NotificationUpdate update = NotificationUpdate.builder() .updated(true) @@ -375,7 +381,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple public void deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId) { Notification notification = notificationService.findNotificationById(tenantId, notificationId); boolean deleted = notificationService.deleteNotification(tenantId, recipientId, notificationId); - if (deleted) { + if (deleted && notification.getDeliveryMethod() == WEB) { NotificationUpdate update = NotificationUpdate.builder() .deleted(true) .notification(notification) @@ -455,7 +461,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple @Override public NotificationDeliveryMethod getDeliveryMethod() { - return NotificationDeliveryMethod.WEB; + return WEB; } @Override @@ -466,7 +472,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple @Autowired public void setChannels(List channels, NotificationCenter webNotificationChannel) { this.channels = channels.stream().collect(Collectors.toMap(NotificationChannel::getDeliveryMethod, c -> c)); - this.channels.put(NotificationDeliveryMethod.WEB, (NotificationChannel) webNotificationChannel); + this.channels.put(WEB, (NotificationChannel) webNotificationChannel); } } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java index 646556372a..f796e2b440 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.notification.channels; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Strings; import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.MessagingErrorCode; @@ -26,11 +27,15 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.notification.FirebaseService; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.settings.MobileAppNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.template.MobileAppDeliveryMethodNotificationTemplate; +import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.service.notification.NotificationProcessingContext; @@ -41,6 +46,8 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.MOBILE_APP; + @Component @RequiredArgsConstructor @Slf4j @@ -48,25 +55,54 @@ public class MobileAppNotificationChannel implements NotificationChannel validTokens = new HashSet<>(mobileSessions.keySet()); String subject = processedTemplate.getSubject(); String body = processedTemplate.getBody(); Map data = getNotificationData(processedTemplate, ctx); + int unreadCount = notificationService.countUnreadNotificationsByRecipientId(ctx.getTenantId(), MOBILE_APP, recipient.getId()); for (String token : mobileSessions.keySet()) { try { - firebaseService.sendMessage(ctx.getTenantId(), credentials, token, subject, body, data); + firebaseService.sendMessage(ctx.getTenantId(), credentials, token, subject, body, data, unreadCount); } catch (FirebaseMessagingException e) { MessagingErrorCode errorCode = e.getMessagingErrorCode(); if (errorCode == MessagingErrorCode.UNREGISTERED || errorCode == MessagingErrorCode.INVALID_ARGUMENT) { @@ -92,12 +128,6 @@ public class MobileAppNotificationChannel implements NotificationChannel { data.put("stateEntityId", stateEntityId.getId().toString()); data.put("stateEntityType", stateEntityId.getEntityType().name()); - if (!"true".equals(data.get("onClick.enabled")) && info.getDashboardId() != null) { - data.put("onClick.enabled", "true"); - data.put("onClick.linkType", "DASHBOARD"); - data.put("onClick.setEntityIdInState", "true"); - data.put("onClick.dashboardId", info.getDashboardId().toString()); - } }); data.put("notificationType", ctx.getNotificationType().name()); switch (ctx.getNotificationType()) { @@ -116,14 +146,14 @@ public class MobileAppNotificationChannel implements NotificationChannel data) throws FirebaseMessagingException { + public void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body, + Map data, Integer badge) throws FirebaseMessagingException { FirebaseContext firebaseContext = contexts.asMap().compute(tenantId.toString(), (key, context) -> { if (context == null) { return new FirebaseContext(key, credentials); @@ -64,6 +65,12 @@ public class DefaultFirebaseService implements FirebaseService { } }); + Aps.Builder apsConfig = Aps.builder() + .setSound("default"); + if (badge != null) { + apsConfig.setBadge(badge); + } + Message message = Message.builder() .setToken(fcmToken) .setNotification(Notification.builder() @@ -74,14 +81,17 @@ public class DefaultFirebaseService implements FirebaseService { .setPriority(AndroidConfig.Priority.HIGH) .build()) .setApnsConfig(ApnsConfig.builder() - .setAps(Aps.builder() - .setContentAvailable(true) - .build()) + .setAps(apsConfig.build()) .build()) .putAllData(data) .build(); - firebaseContext.getMessaging().send(message); - log.trace("[{}] Sent message for FCM token {}", tenantId, fcmToken); + try { + firebaseContext.getMessaging().send(message); + log.trace("[{}] Sent message for FCM token {}", tenantId, fcmToken); + } catch (Throwable t) { + log.debug("[{}] Failed to send message for FCM token {}", tenantId, fcmToken, t); + throw t; + } } public static class FirebaseContext { diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index 13de09bb2d..f0b93a175a 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -114,9 +114,17 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< if (partitionService.isManagedByCurrentService(queueKey.getTenantId())) { var consumer = getConsumer(queueKey).orElseGet(() -> { Queue config = queueService.findQueueByTenantIdAndName(queueKey.getTenantId(), queueKey.getQueueName()); + if (config == null) { + if (!partitions.isEmpty()) { + log.error("[{}] Queue configuration is missing", queueKey, new RuntimeException("stacktrace")); + } + return null; + } return createConsumer(queueKey, config); }); - consumer.update(partitions); + if (consumer != null) { + consumer.update(partitions); + } } }); consumers.keySet().stream() diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java b/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java index c860a00237..5479677f39 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.queue; import io.micrometer.core.instrument.Timer; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.stats.StatsCounter; import org.thingsboard.server.common.stats.StatsFactory; @@ -45,6 +44,7 @@ public class TbRuleEngineConsumerStats { public static final String FAILED_MSGS = "failedMsgs"; public static final String SUCCESSFUL_ITERATIONS = "successfulIterations"; public static final String FAILED_ITERATIONS = "failedIterations"; + public static final String TENANT_ID_TAG = "tenantId"; private final StatsFactory statsFactory; @@ -73,14 +73,15 @@ public class TbRuleEngineConsumerStats { this.statsFactory = statsFactory; String statsKey = StatsType.RULE_ENGINE.getName() + "." + queueName; - this.totalMsgCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS); - this.successMsgCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_MSGS); - this.timeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TIMEOUT_MSGS); - this.failedMsgCounter = statsFactory.createStatsCounter(statsKey, FAILED_MSGS); - this.tmpTimeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_TIMEOUT); - this.tmpFailedMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_FAILED); - this.successIterationsCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_ITERATIONS); - this.failedIterationsCounter = statsFactory.createStatsCounter(statsKey, FAILED_ITERATIONS); + String tenant = tenantId == null || tenantId.isSysTenantId() ? "system" : tenantId.toString(); + this.totalMsgCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS, TENANT_ID_TAG, tenant); + this.successMsgCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_MSGS, TENANT_ID_TAG, tenant); + this.timeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TIMEOUT_MSGS, TENANT_ID_TAG, tenant); + this.failedMsgCounter = statsFactory.createStatsCounter(statsKey, FAILED_MSGS, TENANT_ID_TAG, tenant); + this.tmpTimeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_TIMEOUT, TENANT_ID_TAG, tenant); + this.tmpFailedMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_FAILED, TENANT_ID_TAG, tenant); + this.successIterationsCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_ITERATIONS, TENANT_ID_TAG, tenant); + this.failedIterationsCounter = statsFactory.createStatsCounter(statsKey, FAILED_ITERATIONS, TENANT_ID_TAG, tenant); counters.add(totalMsgCounter); counters.add(successMsgCounter); @@ -93,7 +94,7 @@ public class TbRuleEngineConsumerStats { counters.add(failedIterationsCounter); } - public Timer getTimer(TenantId tenantId, String status){ + public Timer getTimer(TenantId tenantId, String status) { return tenantMsgProcessTimers.computeIfAbsent(tenantId, id -> statsFactory.createTimer(StatsType.RULE_ENGINE.getName() + "." + queueName, "tenantId", tenantId.getId().toString(), diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java index c8683972d9..57ba35628c 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java @@ -68,7 +68,7 @@ public class RpcCleanUpService { long ttl = TimeUnit.DAYS.toMillis(tenantProfileConfiguration.get().getRpcTtlDays()); long expirationTime = System.currentTimeMillis() - ttl; - long totalRemoved = rpcDao.deleteOutdatedRpcByTenantId(tenantId, expirationTime); + int totalRemoved = rpcDao.deleteOutdatedRpcByTenantId(tenantId, expirationTime); if (totalRemoved > 0) { log.info("Removed {} outdated rpc(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime)); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index f868a9d4f3..6fa9111c3d 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -51,6 +51,8 @@ import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.WEB; + @Service @TbCoreComponent @RequiredArgsConstructor @@ -104,7 +106,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void fetchUnreadNotifications(NotificationsSubscription subscription) { log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId()); PageData notifications = notificationService.findLatestUnreadNotificationsByRecipientId(subscription.getTenantId(), - (UserId) subscription.getEntityId(), subscription.getLimit()); + WEB, (UserId) subscription.getEntityId(), subscription.getLimit()); subscription.getLatestUnreadNotifications().clear(); notifications.getData().forEach(notification -> { subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification); @@ -114,7 +116,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void fetchUnreadNotificationsCount(NotificationsCountSubscription subscription) { log.trace("[{}, subId: {}] Fetching unread notifications count from DB", subscription.getSessionId(), subscription.getSubscriptionId()); - int unreadCount = notificationService.countUnreadNotificationsByRecipientId(subscription.getTenantId(), (UserId) subscription.getEntityId()); + int unreadCount = notificationService.countUnreadNotificationsByRecipientId(subscription.getTenantId(), WEB, (UserId) subscription.getEntityId()); subscription.getTotalUnreadCounter().set(unreadCount); } @@ -235,7 +237,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH @Override public void handleMarkAllAsReadCmd(WebSocketSessionRef sessionRef, MarkAllNotificationsAsReadCmd cmd) { SecurityUser securityCtx = sessionRef.getSecurityCtx(); - notificationCenter.markAllNotificationsAsRead(securityCtx.getTenantId(), securityCtx.getId()); + notificationCenter.markAllNotificationsAsRead(securityCtx.getTenantId(), WEB, securityCtx.getId()); } @Override diff --git a/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java index b41c8c3e78..769e056b15 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java @@ -29,6 +29,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.test.context.ContextConfiguration; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.StringUtils; @@ -45,6 +46,7 @@ import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.exception.DataValidationException; @@ -55,6 +57,7 @@ import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService; import java.util.ArrayList; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @@ -327,6 +330,53 @@ public class AssetControllerTest extends AbstractControllerTest { .andExpect(statusReason(containsString(msgErrorNoFound("Alarm", alarm.getId().getId().toString())))); } + @Test + public void testDeleteAssetWithPropagatedAlarm() throws Exception { + Device device = new Device(); + device.setTenantId(savedTenant.getTenantId()); + device.setName("Test device"); + device.setLabel("Label"); + device.setType("default"); + device = doPost("/api/device", device, Device.class); + + Asset asset = new Asset(); + asset.setName("My asset"); + asset.setType("default"); + asset = doPost("/api/asset", asset, Asset.class); + + EntityRelation entityRelation = new EntityRelation(asset.getId(), device.getId(), "CONTAINS"); + doPost("/api/relation", entityRelation); + + //create alarm + Alarm alarm = Alarm.builder() + .tenantId(savedTenant.getTenantId()) + .originator(device.getId()) + .severity(AlarmSeverity.CRITICAL) + .type("test_type") + .propagate(true) + .build(); + + alarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertNotNull(alarm); + + PageData deviceAlarms = doGetTyped("/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=10", new TypeReference<>() { + }); + assertThat(deviceAlarms.getData()).hasSize(1); + + PageData assetAlarms = doGetTyped("/api/alarm/ASSET/" + asset.getUuidId() + "?page=0&pageSize=10", new TypeReference<>() { + }); + assertThat(assetAlarms.getData()).hasSize(1); + + //delete asset + doDelete("/api/asset/" + asset.getId().getId().toString()) + .andExpect(status().isOk()); + + //check device alarms + PageData deviceAlarmsAfterAssetDeletion = doGetTyped("/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=10", new TypeReference>() { + }); + assertThat(deviceAlarmsAfterAssetDeletion.getData()).hasSize(1); + } + @Test public void testDeleteAssetAssignedToEntityView() throws Exception { Asset asset1 = new Asset(); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index b945c162df..ae9dbe92c4 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -145,7 +145,7 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest protected NotificationRequest submitNotificationRequest(List targets, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) { if (deliveryMethods.length == 0) { - deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.WEB}; + deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP}; } NotificationTemplate notificationTemplate = createNotificationTemplate(DEFAULT_NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_SUBJECT, text, deliveryMethods); return submitNotificationRequest(targets, notificationTemplate.getId(), delayInSec); @@ -247,8 +247,12 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest } protected List getMyNotifications(boolean unreadOnly, int limit) throws Exception { - return doGetTypedWithPageLink("/api/notifications?unreadOnly={unreadOnly}&", new TypeReference>() {}, - new PageLink(limit, 0), unreadOnly).getData(); + return getMyNotifications(NotificationDeliveryMethod.WEB, unreadOnly, limit); + } + + protected List getMyNotifications(NotificationDeliveryMethod deliveryMethod, boolean unreadOnly, int limit) throws Exception { + return doGetTypedWithPageLink("/api/notifications?unreadOnly={unreadOnly}&deliveryMethod={deliveryMethod}&", new TypeReference>() {}, + new PageLink(limit, 0), unreadOnly, deliveryMethod).getData(); } protected NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) { diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index d3b79029ba..060af9eac1 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; @@ -52,6 +53,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestPrevie import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationType; +import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo; import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo; import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmCommentNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.settings.MobileAppNotificationDeliveryMethodConfig; @@ -81,7 +83,6 @@ import org.thingsboard.server.common.data.notification.template.WebDeliveryMetho import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.notification.DefaultNotifications; -import org.thingsboard.server.dao.notification.NotificationDao; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.service.notification.channels.MicrosoftTeamsNotificationChannel; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; @@ -116,17 +117,25 @@ public class NotificationApiTest extends AbstractNotificationApiTest { @Autowired private NotificationCenter notificationCenter; @Autowired - private NotificationDao notificationDao; - @Autowired private MicrosoftTeamsNotificationChannel microsoftTeamsNotificationChannel; @MockBean private FirebaseService firebaseService; + private static final String TEST_MOBILE_TOKEN = "tenantFcmToken"; + @Before public void beforeEach() throws Exception { loginCustomerUser(); wsClient = getWsClient(); + + loginSysAdmin(); + MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig(); + config.setFirebaseServiceAccountCredentials("testCredentials"); + saveNotificationSettings(config); + loginTenantAdmin(); + mobileToken = TEST_MOBILE_TOKEN; + doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk()); } @Test @@ -242,7 +251,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { int notificationsCount = 20; wsClient.registerWaitForUpdate(notificationsCount); for (int i = 1; i <= notificationsCount; i++) { - submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB); + submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP); } wsClient.waitForUpdate(true); assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isEqualTo(notificationsCount); @@ -306,7 +315,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { NotificationTarget target = createNotificationTarget(savedDifferentTenantUser.getId()); int notificationsCount = 20; for (int i = 0; i < notificationsCount; i++) { - NotificationRequest request = submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB); + NotificationRequest request = submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP); awaitNotificationRequest(request.getId()); } List requests = notificationRequestService.findNotificationRequestsByTenantIdAndOriginatorType(tenantId, EntityType.USER, new PageLink(100)).getData(); @@ -500,7 +509,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { @Test public void testNotificationRequestInfo() throws Exception { NotificationDeliveryMethod[] deliveryMethods = new NotificationDeliveryMethod[]{ - NotificationDeliveryMethod.WEB + NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP }; NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Test subject", "Test text", deliveryMethods); NotificationTarget target = createNotificationTarget(tenantAdminUserId); @@ -525,7 +534,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest { await().atMost(2, TimeUnit.SECONDS) .until(() -> findNotificationRequest(notificationRequest.getId()).isSent()); NotificationRequestStats stats = getStats(notificationRequest.getId()); - assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(1); } @@ -727,17 +735,12 @@ public class NotificationApiTest extends AbstractNotificationApiTest { @Test public void testMobileAppNotifications() throws Exception { - loginSysAdmin(); - MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig(); - config.setFirebaseServiceAccountCredentials("testCredentials"); - saveNotificationSettings(config); - loginCustomerUser(); mobileToken = "customerFcmToken"; doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk()); loginTenantAdmin(); - mobileToken = "tenantFcmToken1"; + mobileToken = TEST_MOBILE_TOKEN; doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk()); mobileToken = "tenantFcmToken2"; doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk()); @@ -746,7 +749,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { loginTenantAdmin(); NotificationTarget target = createNotificationTarget(new AllUsersFilter()); - NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Title", "Message", NotificationDeliveryMethod.MOBILE_APP); + NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Title", "Message", NotificationDeliveryMethod.MOBILE_APP, NotificationDeliveryMethod.WEB); ((MobileAppDeliveryMethodNotificationTemplate) template.getConfiguration().getDeliveryMethodsTemplates().get(NotificationDeliveryMethod.MOBILE_APP)) .setAdditionalConfig(JacksonUtil.newObjectNode().set("test", JacksonUtil.newObjectNode().put("test", "test"))); saveNotificationTemplate(template); @@ -758,11 +761,19 @@ public class NotificationApiTest extends AbstractNotificationApiTest { .contains("doesn't use the mobile app"); verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"), - eq("tenantFcmToken1"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test")))); + eq(TEST_MOBILE_TOKEN), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))), eq(1)); verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"), - eq("tenantFcmToken2"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test")))); + eq("tenantFcmToken2"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))), eq(1)); verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"), - eq("customerFcmToken"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test")))); + eq("customerFcmToken"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))), eq(1)); + assertThat(getMyNotifications(NotificationDeliveryMethod.MOBILE_APP, true, 10)).singleElement().satisfies(notification -> { + assertThat(notification.getDeliveryMethod()).isEqualTo(NotificationDeliveryMethod.MOBILE_APP); + assertThat(notification.getText()).isEqualTo("Message"); + assertThat(notification.getSubject()).isEqualTo("Title"); + }); + assertThat(getMyNotifications(true, 10)).singleElement().satisfies(notification -> { + assertThat(notification.getDeliveryMethod()).isEqualTo(NotificationDeliveryMethod.WEB); + }); verifyNoMoreInteractions(firebaseService); clearInvocations(firebaseService); @@ -770,26 +781,24 @@ public class NotificationApiTest extends AbstractNotificationApiTest { request = submitNotificationRequest(List.of(target.getId()), template.getId(), 0); awaitNotificationRequest(request.getId()); verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"), - eq("tenantFcmToken1"), eq("Title"), eq("Message"), anyMap()); + eq(TEST_MOBILE_TOKEN), eq("Title"), eq("Message"), anyMap(), eq(2)); verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"), - eq("customerFcmToken"), eq("Title"), eq("Message"), anyMap()); + eq("customerFcmToken"), eq("Title"), eq("Message"), anyMap(), eq(2)); verifyNoMoreInteractions(firebaseService); + + Integer unreadCount = doGet("/api/notifications/unread/count", Integer.class); + assertThat(unreadCount).isEqualTo(2); } @Test public void testMobileAppNotifications_ruleBased() throws Exception { - loginSysAdmin(); - MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig(); - config.setFirebaseServiceAccountCredentials("testCredentials"); - saveNotificationSettings(config); - loginTenantAdmin(); - mobileToken = "tenantFcmToken"; + mobileToken = TEST_MOBILE_TOKEN; doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk()); createNotificationRule(AlarmCommentNotificationRuleTriggerConfig.builder().onlyUserComments(true).build(), DefaultNotifications.alarmComment.getSubject(), DefaultNotifications.alarmComment.getText(), - List.of(createNotificationTarget(tenantAdminUserId).getId()), NotificationDeliveryMethod.MOBILE_APP); + List.of(createNotificationTarget(tenantAdminUserId).getId()), NotificationDeliveryMethod.MOBILE_APP, NotificationDeliveryMethod.WEB); Device device = createDevice("test", "test"); UUID alarmDashboardId = UUID.randomUUID(); @@ -802,18 +811,21 @@ public class NotificationApiTest extends AbstractNotificationApiTest { .put("dashboardId", alarmDashboardId.toString())) .build(); alarm = doPost("/api/alarm", alarm, Alarm.class); + AlarmId alarmId = alarm.getId(); AlarmComment comment = new AlarmComment(); comment.setComment(JacksonUtil.newObjectNode() .put("text", "text")); - doPost("/api/alarm/" + alarm.getId() + "/comment", comment, AlarmComment.class); + doPost("/api/alarm/" + alarmId + "/comment", comment, AlarmComment.class); + String expectedSubject = "Comment on 'test' alarm"; + String expectedBody = TENANT_ADMIN_EMAIL + " added comment: text"; ArgumentCaptor> msgCaptor = ArgumentCaptor.forClass(Map.class); await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> { verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"), - eq("tenantFcmToken"), eq("Comment on 'test' alarm"), - eq(TENANT_ADMIN_EMAIL + " added comment: text"), - msgCaptor.capture()); + eq(TEST_MOBILE_TOKEN), eq(expectedSubject), + eq(expectedBody), + msgCaptor.capture(), eq(1)); }); Map firebaseMessageData = msgCaptor.getValue(); assertThat(firebaseMessageData.keySet()).doesNotContainNull().doesNotContain(""); @@ -823,6 +835,14 @@ public class NotificationApiTest extends AbstractNotificationApiTest { assertThat(firebaseMessageData.get("onClick.enabled")).isEqualTo("true"); assertThat(firebaseMessageData.get("onClick.linkType")).isEqualTo("DASHBOARD"); assertThat(firebaseMessageData.get("onClick.dashboardId")).isEqualTo(alarmDashboardId.toString()); + + assertThat(getMyNotifications(NotificationDeliveryMethod.MOBILE_APP, true, 10)).singleElement().satisfies(notification -> { + assertThat(notification.getDeliveryMethod()).isEqualTo(NotificationDeliveryMethod.MOBILE_APP); + assertThat(notification.getSubject()).isEqualTo(expectedSubject); + assertThat(notification.getText()).isEqualTo(expectedBody); + assertThat(notification.getInfo()).asInstanceOf(type(AlarmCommentNotificationInfo.class)) + .matches(info -> info.getAlarmId().equals(alarmId.getId()) && info.getDashboardId().getId().equals(alarmDashboardId)); + }); } @Test 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 203fe8e744..a6e6e2390b 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 @@ -106,6 +106,8 @@ public interface AlarmService extends EntityDaoService { PageData findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, PageLink pageLink); + PageData findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, PageLink pageLink); + void deleteEntityAlarmRelations(TenantId tenantId, EntityId entityId); void deleteEntityAlarmRecordsByTenantId(TenantId tenantId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java index 18c9d4a7fb..91f6f7a379 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -30,13 +31,13 @@ public interface NotificationService { boolean markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId); - int markAllNotificationsAsRead(TenantId tenantId, UserId recipientId); + int markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId); - PageData findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, UserId recipientId, boolean unreadOnly, PageLink pageLink); + PageData findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, boolean unreadOnly, PageLink pageLink); - PageData findLatestUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId, int limit); + PageData findLatestUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, int limit); - int countUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId); + int countUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId); boolean deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java index 0f7095c755..6335cb671d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java @@ -36,13 +36,12 @@ public class Notification extends BaseData { private NotificationRequestId requestId; private UserId recipientId; - private NotificationType type; + private NotificationDeliveryMethod deliveryMethod; private String subject; private String text; private JsonNode additionalConfig; private NotificationInfo info; - private NotificationStatus status; } diff --git a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java index 3c4a1e6be9..698c321625 100644 --- a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java @@ -111,7 +111,7 @@ public class EdgeGrpcClient implements EdgeRpcClient { .setConnectRequestMsg(ConnectRequestMsg.newBuilder() .setEdgeRoutingKey(edgeKey) .setEdgeSecret(edgeSecret) - .setEdgeVersion(EdgeVersion.V_3_6_2) + .setEdgeVersion(EdgeVersion.V_3_6_4) .setMaxInboundMessageSize(maxInboundMessageSize) .build()) .build()); diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 34a8c6f093..00cf838fd7 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -37,6 +37,7 @@ enum EdgeVersion { V_3_6_0 = 3; V_3_6_1 = 4; V_3_6_2 = 5; + V_3_6_4 = 6; } /** diff --git a/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java b/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java index a6ec01d089..e3ef373014 100644 --- a/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java +++ b/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java @@ -19,6 +19,7 @@ import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Timer; +import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -61,12 +62,17 @@ public class DefaultStatsFactory implements StatsFactory { @Override - public StatsCounter createStatsCounter(String key, String statsName) { + public StatsCounter createStatsCounter(String key, String statsName, String... otherTags) { + String[] tags = new String[]{STATS_NAME_TAG, statsName}; + if (otherTags.length > 0) { + if (otherTags.length % 2 != 0) { + throw new IllegalArgumentException("Invalid tags array size"); + } + tags = ArrayUtils.addAll(tags, otherTags); + } return new StatsCounter( new AtomicInteger(0), - metricsEnabled ? - meterRegistry.counter(key, STATS_NAME_TAG, statsName) - : STUB_COUNTER, + metricsEnabled ? meterRegistry.counter(key, tags) : STUB_COUNTER, statsName ); } diff --git a/common/stats/src/main/java/org/thingsboard/server/common/stats/StatsFactory.java b/common/stats/src/main/java/org/thingsboard/server/common/stats/StatsFactory.java index 8e6989016b..b9bf0b97fb 100644 --- a/common/stats/src/main/java/org/thingsboard/server/common/stats/StatsFactory.java +++ b/common/stats/src/main/java/org/thingsboard/server/common/stats/StatsFactory.java @@ -18,7 +18,8 @@ package org.thingsboard.server.common.stats; import io.micrometer.core.instrument.Timer; public interface StatsFactory { - StatsCounter createStatsCounter(String key, String statsName); + + StatsCounter createStatsCounter(String key, String statsName, String... otherTags); DefaultCounter createDefaultCounter(String key, String... tags); @@ -27,4 +28,5 @@ public interface StatsFactory { MessagesStats createMessagesStats(String key); Timer createTimer(String key, String... tags); + } diff --git a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java index 53c3860dde..d71d97b1e4 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java @@ -231,6 +231,10 @@ public class JacksonUtil { return node; } + public static ObjectNode asObject(JsonNode node) { + return node != null && node.isObject() ? ((ObjectNode) node) : newObjectNode(); + } + public static void replaceUuidsRecursively(JsonNode node, Set skippedRootFields, Pattern includedFieldsPattern, UnaryOperator replacer, boolean root) { if (node == null) { return; diff --git a/common/util/src/main/java/org/thingsboard/common/util/SslUtil.java b/common/util/src/main/java/org/thingsboard/common/util/SslUtil.java index d58f203956..4ef6fae706 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/SslUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/SslUtil.java @@ -73,7 +73,7 @@ public class SslUtil { @SneakyThrows public static PrivateKey readPrivateKey(String fileContent, String passStr) { - char[] password = StringUtils.isEmpty(passStr) ? EMPTY_PASS : passStr.toCharArray(); + char[] password = getPassword(passStr); PrivateKey privateKey = null; JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter(); @@ -102,4 +102,8 @@ public class SslUtil { return privateKey; } + public static char[] getPassword(String passStr) { + return StringUtils.isEmpty(passStr) ? EMPTY_PASS : passStr.toCharArray(); + } + } diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java index 24d23c60a1..6b6812b8ef 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java @@ -157,12 +157,10 @@ public class DefaultGitRepositoryService implements GitRepositoryService { @Override public void fetch(TenantId tenantId) throws GitAPIException { - var repository = repositories.get(tenantId); - if (repository != null) { - log.debug("[{}] Fetching tenant repository.", tenantId); - repository.fetch(); - log.debug("[{}] Fetched tenant repository.", tenantId); - } + var repository = checkRepository(tenantId); + log.debug("[{}] Fetching tenant repository.", tenantId); + repository.fetch(); + log.debug("[{}] Fetched tenant repository.", tenantId); } @Override @@ -195,8 +193,17 @@ public class DefaultGitRepositoryService implements GitRepositoryService { } private GitRepository checkRepository(TenantId tenantId) { - return Optional.ofNullable(repositories.get(tenantId)) + GitRepository gitRepository = Optional.ofNullable(repositories.get(tenantId)) .orElseThrow(() -> new IllegalStateException("Repository is not initialized")); + + if (!Files.exists(Path.of(gitRepository.getDirectory()))) { + try { + return cloneRepository(tenantId, gitRepository.getSettings()); + } catch (Exception e) { + throw new IllegalStateException("Could not initialize the repository: " + e.getMessage(), e); + } + } + return gitRepository; } @Override @@ -229,17 +236,7 @@ public class DefaultGitRepositoryService implements GitRepositoryService { testRepository(tenantId, settings); clearRepository(tenantId); - log.debug("[{}] Init tenant repository started.", tenantId); - Path repositoryDirectory = Path.of(repositoriesFolder, tenantId.getId().toString()); - GitRepository repository; - if (Files.exists(repositoryDirectory)) { - FileUtils.forceDelete(repositoryDirectory.toFile()); - } - - Files.createDirectories(repositoryDirectory); - repository = GitRepository.clone(settings, repositoryDirectory.toFile()); - repositories.put(tenantId, repository); - log.debug("[{}] Init tenant repository completed.", tenantId); + cloneRepository(tenantId, settings); } @Override @@ -276,4 +273,18 @@ public class DefaultGitRepositoryService implements GitRepositoryService { String entityId = StringUtils.substringBetween(path, "/", ".json"); return EntityIdFactory.getByTypeAndUuid(entityType, entityId); } + + private GitRepository cloneRepository(TenantId tenantId, RepositorySettings settings) throws Exception { + log.debug("[{}] Init tenant repository started.", tenantId); + Path repositoryDirectory = Path.of(repositoriesFolder, tenantId.getId().toString()); + + if (Files.exists(repositoryDirectory)) { + FileUtils.forceDelete(repositoryDirectory.toFile()); + } + Files.createDirectories(repositoryDirectory); + GitRepository repository = GitRepository.clone(settings, repositoryDirectory.toFile()); + repositories.put(tenantId, repository); + log.debug("[{}] Init tenant repository completed.", tenantId); + return repository; + } } 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 c752ae706d..19b02fc932 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 @@ -80,6 +80,8 @@ public interface AlarmDao extends Dao { PageData findAlarmIdsByAssigneeId(TenantId tenantId, UUID userId, PageLink pageLink); + PageData findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, PageLink pageLink); + void createEntityAlarmRecord(EntityAlarm entityAlarm); List findEntityAlarmRecords(TenantId tenantId, AlarmId id); 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 72e1b9c7bf..8fd0f20252 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 @@ -306,6 +306,12 @@ public class BaseAlarmService extends AbstractCachedEntityService findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, PageLink pageLink) { + log.trace("[{}] Executing findAlarmsByOriginatorId [{}]", tenantId, originatorId); + return alarmDao.findAlarmIdsByOriginatorId(tenantId, originatorId, pageLink); + } + @Override public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId) { 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 4ee812a26b..b91e2ed96d 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.entity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.HasCustomerId; @@ -60,6 +61,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe private EntityQueryDao entityQueryDao; @Autowired + @Lazy EntityServiceRegistry entityServiceRegistry; @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityServiceRegistry.java b/dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityServiceRegistry.java index 6e6233b1b2..ed0f14ad6d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityServiceRegistry.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityServiceRegistry.java @@ -17,15 +17,12 @@ package org.thingsboard.server.dao.entity; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.context.ApplicationContext; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.context.event.EventListener; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; +import javax.annotation.PostConstruct; import java.util.HashMap; +import java.util.List; import java.util.Map; @Service @@ -33,14 +30,13 @@ import java.util.Map; @Slf4j public class DefaultEntityServiceRegistry implements EntityServiceRegistry { - private final ApplicationContext applicationContext; + private final List entityDaoServices; private final Map entityDaoServicesMap = new HashMap<>(); - @EventListener(ContextRefreshedEvent.class) - @Order(Ordered.HIGHEST_PRECEDENCE) + @PostConstruct public void init() { log.debug("Initializing EntityServiceRegistry on ContextRefreshedEvent"); - applicationContext.getBeansOfType(EntityDaoService.class).values().forEach(entityDaoService -> { + entityDaoServices.forEach(entityDaoService -> { EntityType entityType = entityDaoService.getEntityType(); entityDaoServicesMap.put(entityType, entityDaoService); if (EntityType.RULE_CHAIN.equals(entityType)) { 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 2fe56f5d1f..d0e8c7c394 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 @@ -620,6 +620,7 @@ public class ModelConstants { public static final String NOTIFICATION_REQUEST_ID_PROPERTY = "request_id"; public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id"; public static final String NOTIFICATION_TYPE_PROPERTY = "type"; + public static final String NOTIFICATION_DELIVERY_METHOD_PROPERTY = "delivery_method"; public static final String NOTIFICATION_SUBJECT_PROPERTY = "subject"; public static final String NOTIFICATION_TEXT_PROPERTY = "body"; public static final String NOTIFICATION_ADDITIONAL_CONFIG_PROPERTY = "additional_config"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java index 82c0ee0ab9..bf818b0f64 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.info.NotificationInfo; @@ -56,6 +57,10 @@ public class NotificationEntity extends BaseSqlEntity { @Column(name = ModelConstants.NOTIFICATION_TYPE_PROPERTY, nullable = false) private NotificationType type; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.NOTIFICATION_DELIVERY_METHOD_PROPERTY, nullable = false) + private NotificationDeliveryMethod deliveryMethod; + @Column(name = ModelConstants.NOTIFICATION_SUBJECT_PROPERTY) private String subject; @@ -82,6 +87,7 @@ public class NotificationEntity extends BaseSqlEntity { setRequestId(getUuid(notification.getRequestId())); setRecipientId(getUuid(notification.getRecipientId())); setType(notification.getType()); + setDeliveryMethod(notification.getDeliveryMethod()); setSubject(notification.getSubject()); setText(notification.getText()); setAdditionalConfig(notification.getAdditionalConfig()); @@ -97,6 +103,7 @@ public class NotificationEntity extends BaseSqlEntity { notification.setRequestId(getEntityId(requestId, NotificationRequestId::new)); notification.setRecipientId(getEntityId(recipientId, UserId::new)); notification.setType(type); + notification.setDeliveryMethod(deliveryMethod); notification.setSubject(subject); notification.setText(text); notification.setAdditionalConfig(additionalConfig); diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java index b62d9a83ad..38a8cb4178 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -57,29 +58,29 @@ public class DefaultNotificationService implements NotificationService, EntityDa } @Override - public int markAllNotificationsAsRead(TenantId tenantId, UserId recipientId) { - return notificationDao.updateStatusByRecipientId(tenantId, recipientId, NotificationStatus.READ); + public int markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) { + return notificationDao.updateStatusByDeliveryMethodAndRecipientId(tenantId, deliveryMethod, recipientId, NotificationStatus.READ); } @Override - public PageData findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, UserId recipientId, boolean unreadOnly, PageLink pageLink) { + public PageData findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, boolean unreadOnly, PageLink pageLink) { if (unreadOnly) { - return notificationDao.findUnreadByRecipientIdAndPageLink(tenantId, recipientId, pageLink); + return notificationDao.findUnreadByDeliveryMethodAndRecipientIdAndPageLink(tenantId, deliveryMethod, recipientId, pageLink); } else { - return notificationDao.findByRecipientIdAndPageLink(tenantId, recipientId, pageLink); + return notificationDao.findByDeliveryMethodAndRecipientIdAndPageLink(tenantId, deliveryMethod, recipientId, pageLink); } } @Override - public PageData findLatestUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId, int limit) { + public PageData findLatestUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, int limit) { SortOrder sortOrder = new SortOrder(EntityKeyMapping.CREATED_TIME, SortOrder.Direction.DESC); PageLink pageLink = new PageLink(limit, 0, null, sortOrder); - return findNotificationsByRecipientIdAndReadStatus(tenantId, recipientId, true, pageLink); + return findNotificationsByRecipientIdAndReadStatus(tenantId, deliveryMethod, recipientId, true, pageLink); } @Override - public int countUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId) { - return notificationDao.countUnreadByRecipientId(tenantId, recipientId); + public int countUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) { + return notificationDao.countUnreadByDeliveryMethodAndRecipientId(tenantId, deliveryMethod, recipientId); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java index 02bfe11fde..2233840058 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -27,19 +28,17 @@ import org.thingsboard.server.dao.Dao; public interface NotificationDao extends Dao { - PageData findUnreadByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink); + PageData findUnreadByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink); - PageData findByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink); + PageData findByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink); boolean updateStatusByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId, NotificationStatus status); - int countUnreadByRecipientId(TenantId tenantId, UserId recipientId); - - PageData findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink); + int countUnreadByDeliveryMethodAndRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId); boolean deleteByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId); - int updateStatusByRecipientId(TenantId tenantId, UserId recipientId, NotificationStatus status); + int updateStatusByDeliveryMethodAndRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, NotificationStatus status); void deleteByRequestId(TenantId tenantId, NotificationRequestId requestId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java b/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java index 79b73c69aa..d2e867e763 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java @@ -30,5 +30,6 @@ public interface RpcDao extends Dao { PageData findAllRpcByTenantId(TenantId tenantId, PageLink pageLink); - Long deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime); + int deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime); + } 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 655162ef6d..03336affc3 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 @@ -321,6 +321,9 @@ public interface AlarmRepository extends JpaRepository { @Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.assigneeId = :assigneeId") Page findAlarmIdsByAssigneeId(@Param("tenantId") UUID tenantId, @Param("assigneeId") UUID assigneeId, Pageable pageable); + @Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.originatorId = :originatorId") + Page findAlarmIdsByOriginatorId(@Param("tenantId") UUID tenantId, @Param("originatorId") UUID originatorId, Pageable pageable); + @Query(value = "SELECT create_or_update_active_alarm(:t_id, :c_id, :a_id, :a_created_ts, :a_o_id, :a_o_type, :a_type, :a_severity, " + ":a_start_ts, :a_end_ts, :a_details, :a_propagate, :a_propagate_to_owner, " + ":a_propagate_to_tenant, :a_propagation_types, :a_creation_enabled)", nativeQuery = true) 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 9430773ff9..1a2ea99842 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 @@ -296,6 +296,12 @@ public class JpaAlarmDao extends JpaAbstractDao implements A .mapData(AlarmId::new); } + @Override + public PageData findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, PageLink pageLink) { + return DaoUtil.pageToPageData(alarmRepository.findAlarmIdsByOriginatorId(tenantId.getId(), originatorId.getId(), DaoUtil.toPageable(pageLink))) + .mapData(AlarmId::new); + } + @Override public void createEntityAlarmRecord(EntityAlarm entityAlarm) { log.debug("Saving entity {}", entityAlarm); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index f095aad1ae..64d663b397 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -51,14 +52,14 @@ public class JpaNotificationDao extends JpaPartitionedAbstractDao findUnreadByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink) { - return DaoUtil.toPageData(notificationRepository.findByRecipientIdAndStatusNot(recipientId.getId(), NotificationStatus.READ, - pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); + public PageData findUnreadByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink) { + return DaoUtil.toPageData(notificationRepository.findByDeliveryMethodAndRecipientIdAndStatusNot(deliveryMethod, + recipientId.getId(), NotificationStatus.READ, pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); } @Override - public PageData findByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink) { - return DaoUtil.toPageData(notificationRepository.findByRecipientId(recipientId.getId(), + public PageData findByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink) { + return DaoUtil.toPageData(notificationRepository.findByDeliveryMethodAndRecipientId(deliveryMethod, recipientId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); } @@ -71,13 +72,8 @@ public class JpaNotificationDao extends JpaPartitionedAbstractDao findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink) { - return DaoUtil.toPageData(notificationRepository.findByRequestId(notificationRequestId.getId(), DaoUtil.toPageable(pageLink))); + public int countUnreadByDeliveryMethodAndRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) { + return notificationRepository.countByDeliveryMethodAndRecipientIdAndStatusNot(deliveryMethod, recipientId.getId(), NotificationStatus.READ); } @Override @@ -86,8 +82,8 @@ public class JpaNotificationDao extends JpaPartitionedAbstractDao { - @Query("SELECT n FROM NotificationEntity n WHERE n.recipientId = :recipientId AND n.status <> :status " + + @Query("SELECT n FROM NotificationEntity n WHERE n.deliveryMethod = :deliveryMethod " + + "AND n.recipientId = :recipientId AND n.status <> :status " + "AND (:searchText is NULL OR ilike(n.subject, concat('%', :searchText, '%')) = true " + "OR ilike(n.text, concat('%', :searchText, '%')) = true)") - Page findByRecipientIdAndStatusNot(@Param("recipientId") UUID recipientId, - @Param("status") NotificationStatus status, - @Param("searchText") String searchText, - Pageable pageable); + Page findByDeliveryMethodAndRecipientIdAndStatusNot(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod, + @Param("recipientId") UUID recipientId, + @Param("status") NotificationStatus status, + @Param("searchText") String searchText, + Pageable pageable); - @Query("SELECT n FROM NotificationEntity n WHERE n.recipientId = :recipientId " + + @Query("SELECT n FROM NotificationEntity n WHERE n.deliveryMethod = :deliveryMethod AND n.recipientId = :recipientId " + "AND (:searchText is NULL OR ilike(n.subject, concat('%', :searchText, '%')) = true " + "OR ilike(n.text, concat('%', :searchText, '%')) = true)") - Page findByRecipientId(@Param("recipientId") UUID recipientId, - @Param("searchText") String searchText, - Pageable pageable); + Page findByDeliveryMethodAndRecipientId(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod, + @Param("recipientId") UUID recipientId, + @Param("searchText") String searchText, + Pageable pageable); @Modifying @Transactional @@ -54,9 +58,7 @@ public interface NotificationRepository extends JpaRepository findByRequestId(UUID requestId, Pageable pageable); + int countByDeliveryMethodAndRecipientIdAndStatusNot(NotificationDeliveryMethod deliveryMethod, UUID recipientId, NotificationStatus status); @Transactional @Modifying @@ -76,8 +78,9 @@ public interface NotificationRepository extends JpaRepository :status") - int updateStatusByRecipientId(@Param("recipientId") UUID recipientId, - @Param("status") NotificationStatus status); + "WHERE n.deliveryMethod = :deliveryMethod AND n.recipientId = :recipientId AND n.status <> :status") + int updateStatusByDeliveryMethodAndRecipientIdAndStatusNot(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod, + @Param("recipientId") UUID recipientId, + @Param("status") NotificationStatus status); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java index 2c16db4d34..e1939a595c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -67,8 +68,9 @@ public class JpaRpcDao extends JpaAbstractDao implements RpcDao return DaoUtil.toPageData(rpcRepository.findAllByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink))); } + @Transactional @Override - public Long deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime) { + public int deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime) { return rpcRepository.deleteOutdatedRpcByTenantId(tenantId.getId(), expirationTime); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java index e04d000a65..fe7c95d249 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.rpc; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.rpc.RpcStatus; @@ -32,7 +33,8 @@ public interface RpcRepository extends JpaRepository { Page findAllByTenantId(UUID tenantId, Pageable pageable); - @Query(value = "WITH deleted AS (DELETE FROM rpc WHERE (tenant_id = :tenantId AND created_time < :expirationTime) IS TRUE RETURNING *) SELECT count(*) FROM deleted", + @Modifying + @Query(value = "DELETE FROM rpc WHERE tenant_id = :tenantId AND created_time < :expirationTime", nativeQuery = true) - Long deleteOutdatedRpcByTenantId(@Param("tenantId") UUID tenantId, @Param("expirationTime") Long expirationTime); + int deleteOutdatedRpcByTenantId(@Param("tenantId") UUID tenantId, @Param("expirationTime") Long expirationTime); } diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index a177c1325f..6b4f1a6c09 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -118,9 +118,9 @@ CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_id); -CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC); +CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_created_time ON notification(delivery_method, recipient_id, created_time DESC); -CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ'; +CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_unread ON notification(delivery_method, recipient_id) WHERE status <> 'READ'; CREATE INDEX IF NOT EXISTS idx_resource_etag ON resource(tenant_id, etag); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index cdd4edea06..34320ccd9e 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -864,6 +864,7 @@ CREATE TABLE IF NOT EXISTS notification ( request_id UUID, recipient_id UUID NOT NULL, type VARCHAR(50) NOT NULL, + delivery_method VARCHAR(50) NOT NULL, subject VARCHAR(255), body VARCHAR(1000) NOT NULL, additional_config VARCHAR(1000), diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDaoTest.java new file mode 100644 index 0000000000..d2889dfb3c --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDaoTest.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.rpc; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.dao.AbstractJpaDaoTest; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JpaRpcDaoTest extends AbstractJpaDaoTest { + + @Autowired + JpaRpcDao rpcDao; + + @Test + public void deleteOutdated() { + Rpc rpc = new Rpc(); + rpc.setTenantId(TenantId.SYS_TENANT_ID); + rpc.setDeviceId(new DeviceId(UUID.randomUUID())); + rpc.setStatus(RpcStatus.QUEUED); + rpc.setRequest(JacksonUtil.toJsonNode("{}")); + rpcDao.saveAndFlush(rpc.getTenantId(), rpc); + + rpc.setId(null); + rpcDao.saveAndFlush(rpc.getTenantId(), rpc); + + TenantId tenantId = TenantId.fromUUID(UUID.fromString("3d193a7a-774b-4c05-84d5-f7fdcf7a37cf")); + rpc.setId(null); + rpc.setTenantId(tenantId); + rpc.setDeviceId(new DeviceId(UUID.randomUUID())); + rpcDao.saveAndFlush(rpc.getTenantId(), rpc); + + assertThat(rpcDao.deleteOutdatedRpcByTenantId(TenantId.SYS_TENANT_ID, 0L)).isEqualTo(0); + assertThat(rpcDao.deleteOutdatedRpcByTenantId(TenantId.SYS_TENANT_ID, Long.MAX_VALUE)).isEqualTo(2); + assertThat(rpcDao.deleteOutdatedRpcByTenantId(tenantId, System.currentTimeMillis() + 1)).isEqualTo(1); + } + +} diff --git a/dao/src/test/resources/logback.xml b/dao/src/test/resources/logback.xml index 61397ec6f1..5e293b2982 100644 --- a/dao/src/test/resources/logback.xml +++ b/dao/src/test/resources/logback.xml @@ -10,6 +10,9 @@ + + + diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index c7edb2bbd7..01da4a356c 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -15,7 +15,7 @@ "dependencies": { "@aws-sdk/client-sqs": "^3.121.0", "@azure/service-bus": "^7.5.1", - "@google-cloud/pubsub": "^3.0.1", + "@google-cloud/pubsub": "^4.3.3", "amqplib": "^0.10.0", "config": "^3.3.7", "express": "^4.18.1", diff --git a/msa/js-executor/yarn.lock b/msa/js-executor/yarn.lock index eb6443e53d..42a32a93c4 100644 --- a/msa/js-executor/yarn.lock +++ b/msa/js-executor/yarn.lock @@ -797,68 +797,68 @@ enabled "2.0.x" kuler "^2.0.0" -"@google-cloud/paginator@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-4.0.0.tgz#9c3e01544717aecb9a922b4269ff298f30a0f1bb" - integrity sha512-wNmCZl+2G2DmgT/VlF+AROf80SoaC/CwS8trwmjNaq26VRNK8yPbU5F/Vy+R9oDAGKWQU2k8+Op5H4kFJVXFaQ== +"@google-cloud/paginator@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-5.0.0.tgz#b8cc62f151685095d11467402cbf417c41bf14e6" + integrity sha512-87aeg6QQcEPxGCOthnpUjvw4xAZ57G7pL8FS0C4e/81fr3FjkpUpibf1s2v5XGyGhUVGF4Jfg7yEcxqn2iUw1w== dependencies: arrify "^2.0.0" extend "^3.0.2" -"@google-cloud/precise-date@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@google-cloud/precise-date/-/precise-date-2.0.3.tgz#14f6f28ce35dabf3882e7aeab1c9d51bd473faed" - integrity sha512-+SDJ3ZvGkF7hzo6BGa8ZqeK3F6Z4+S+KviC9oOK+XCs3tfMyJCh/4j93XIWINgMMDIh9BgEvlw4306VxlXIlYA== +"@google-cloud/precise-date@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@google-cloud/precise-date/-/precise-date-4.0.0.tgz#e179893a3ad628b17a6fabdfcc9d468753aac11a" + integrity sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA== -"@google-cloud/projectify@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-2.0.1.tgz#13350ee609346435c795bbfe133a08dfeab78d65" - integrity sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ== +"@google-cloud/projectify@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-4.0.0.tgz#d600e0433daf51b88c1fa95ac7f02e38e80a07be" + integrity sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA== -"@google-cloud/promisify@^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-2.0.2.tgz#81d654b4cb227c65c7ad2f9a7715262febd409ed" - integrity sha512-EvuabjzzZ9E2+OaYf+7P9OAiiwbTxKYL0oGLnREQd+Su2NTQBpomkdlkBowFvyWsaV0d1sSGxrKpSNcrhPqbxg== +"@google-cloud/promisify@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-4.0.0.tgz#a906e533ebdd0f754dca2509933334ce58b8c8b1" + integrity sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g== -"@google-cloud/pubsub@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@google-cloud/pubsub/-/pubsub-3.0.1.tgz#3a6bb9649a1b4309d82d8670a9c01375e622692f" - integrity sha512-dznNbRd/Y8J0C0xvdvCPi3B1msK/dj/Nya+NQZ2doUOLT6eoa261tBwk9umOQs5L5GKcdlqQKbBjrNjDYVbzQA== +"@google-cloud/pubsub@^4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@google-cloud/pubsub/-/pubsub-4.3.3.tgz#3d3f947ae8fca1694388592f22f77fdc3008ee8d" + integrity sha512-vJKh9L4dHf1XGSDKS1SB0IpqP/sUajQh4/QwhYasuq/NjzfHSxqSt+CuhrFGb5/gioTWE4gce0sn7h1SW7qESg== dependencies: - "@google-cloud/paginator" "^4.0.0" - "@google-cloud/precise-date" "^2.0.0" - "@google-cloud/projectify" "^2.0.0" - "@google-cloud/promisify" "^2.0.0" - "@opentelemetry/api" "^1.0.0" - "@opentelemetry/semantic-conventions" "^1.0.0" + "@google-cloud/paginator" "^5.0.0" + "@google-cloud/precise-date" "^4.0.0" + "@google-cloud/projectify" "^4.0.0" + "@google-cloud/promisify" "^4.0.0" + "@opentelemetry/api" "^1.6.0" + "@opentelemetry/semantic-conventions" "~1.21.0" "@types/duplexify" "^3.6.0" "@types/long" "^4.0.0" arrify "^2.0.0" extend "^3.0.2" - google-auth-library "^8.0.2" - google-gax "^3.0.1" + google-auth-library "^9.3.0" + google-gax "^4.3.1" + heap-js "^2.2.0" is-stream-ended "^0.1.4" lodash.snakecase "^4.1.1" p-defer "^3.0.0" -"@grpc/grpc-js@~1.6.0": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.6.7.tgz#4c4fa998ff719fe859ac19fe977fdef097bb99aa" - integrity sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw== +"@grpc/grpc-js@~1.10.0": + version "1.10.6" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.10.6.tgz#1e3eb1af911dc888fbef7452f56a7573b8284d54" + integrity sha512-xP58G7wDQ4TCmN/cMUHh00DS7SRDv/+lC+xFLrTkMIN8h55X5NhZMLYbvy7dSELP15qlI6hPhNCRWVMtZMwqLA== dependencies: - "@grpc/proto-loader" "^0.6.4" - "@types/node" ">=12.12.47" + "@grpc/proto-loader" "^0.7.10" + "@js-sdsl/ordered-map" "^4.4.2" -"@grpc/proto-loader@^0.6.12", "@grpc/proto-loader@^0.6.4": - version "0.6.13" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.13.tgz#008f989b72a40c60c96cd4088522f09b05ac66bc" - integrity sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g== +"@grpc/proto-loader@^0.7.0", "@grpc/proto-loader@^0.7.10": + version "0.7.12" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.12.tgz#787b58e3e3771df30b1567c057b6ab89e3a42911" + integrity sha512-DCVwMxqYzpUCiDMl7hQ384FqP4T3DbNpXU8pt681l3UWCip1WUiD5JrkImUwCB9a7f2cq4CUTmi5r/xIMRPY1Q== dependencies: - "@types/long" "^4.0.1" lodash.camelcase "^4.3.0" - long "^4.0.0" - protobufjs "^6.11.3" - yargs "^16.2.0" + long "^5.0.0" + protobufjs "^7.2.4" + yargs "^17.7.2" "@jridgewell/resolve-uri@^3.0.3": version "3.0.8" @@ -878,6 +878,11 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@js-sdsl/ordered-map@^4.4.2": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c" + integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw== + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -899,15 +904,20 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" -"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.0.1": +"@opentelemetry/api@^1.0.1": version "1.1.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.1.0.tgz#563539048255bbe1a5f4f586a4a10a1bb737f44a" integrity sha512-hf+3bwuBwtXsugA2ULBc95qxrOqP2pOekLz34BJhcAKawt94vfeNyUKpYc0lZQ/3sCP6LqRa7UAdHA7i5UODzQ== -"@opentelemetry/semantic-conventions@^1.0.0": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.3.1.tgz#ba07b864a3c955f061aa30ea3ef7f4ae4449794a" - integrity sha512-wU5J8rUoo32oSef/rFpOT1HIjLjAv3qIDHkw1QIhODV3OpAVHi5oVzlouozg9obUmZKtbZ0qUe/m7FP0y0yBzA== +"@opentelemetry/api@^1.6.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.8.0.tgz#5aa7abb48f23f693068ed2999ae627d2f7d902ec" + integrity sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w== + +"@opentelemetry/semantic-conventions@~1.21.0": + version "1.21.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.21.0.tgz#83f7479c524ab523ac2df702ade30b9724476c72" + integrity sha512-lkC8kZYntxVKr7b8xmjCVUgE0a8xgDakPyDo9uSWavXPyYqLgYYGdEd2j8NxihRyb6UwpX3G/hFUF4/9q2V+/g== "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" @@ -974,6 +984,11 @@ dependencies: defer-to-connect "^1.0.1" +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -1015,6 +1030,11 @@ "@types/connect" "*" "@types/node" "*" +"@types/caseless@*": + version "0.12.5" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.5.tgz#db9468cb1b1b5a925b8f34822f1669df0c5472f5" + integrity sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg== + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -1088,7 +1108,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.1.tgz#fdf6f6c6c73d3d8eee9c98a9a0485bc524b048d7" integrity sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ== -"@types/node@>=12.12.47", "@types/node@>=13.7.0": +"@types/node@>=13.7.0": version "17.0.42" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.42.tgz#d7e8f22700efc94d125103075c074396b5f41f9b" integrity sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ== @@ -1108,6 +1128,16 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== +"@types/request@^2.48.8": + version "2.48.12" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.12.tgz#0f590f615a10f87da18e9790ac94c29ec4c5ef30" + integrity sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw== + dependencies: + "@types/caseless" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + form-data "^2.5.0" + "@types/serve-static@*": version "1.13.10" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" @@ -1116,6 +1146,11 @@ "@types/mime" "^1" "@types/node" "*" +"@types/tough-cookie@*": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" + integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== + "@types/tunnel@^0.0.3": version "0.0.3" resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.3.tgz#f109e730b072b3136347561fc558c9358bb8c6e9" @@ -1167,6 +1202,13 @@ agent-base@6: dependencies: debug "4" +agent-base@^7.0.2: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== + dependencies: + debug "^4.3.4" + amqplib@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/amqplib/-/amqplib-0.10.0.tgz#766d696f8ceae097ee9eb73e6796999e5d40a1db" @@ -1462,6 +1504,15 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" @@ -1522,7 +1573,7 @@ colorspace@1.1.x: color "3.0.x" text-hex "1.0.x" -combined-stream@^1.0.8: +combined-stream@^1.0.6, combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -1609,7 +1660,7 @@ debug@2.6.9, debug@~2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.1: +debug@4: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -1623,6 +1674,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" @@ -1921,11 +1979,6 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" - integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== - fast-xml-parser@3.19.0: version "3.19.0" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz#cb637ec3f3999f51406dd8ff0e6fc4d83e520d01" @@ -1982,6 +2035,15 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +form-data@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + form-data@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" @@ -2081,34 +2143,23 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gaxios@^4.0.0: - version "4.3.3" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-4.3.3.tgz#d44bdefe52d34b6435cc41214fdb160b64abfc22" - integrity sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA== +gaxios@^6.0.0, gaxios@^6.1.1: + version "6.4.0" + resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.4.0.tgz#08a42cb44d5123a72efaaf9f786c266e7f18be70" + integrity sha512-apAloYrY4dlBGlhauDAYSZveafb5U6+L9titing1wox6BvWM0TSXBp603zTrLpyLMGkrcFgohnUN150dFN/zOA== dependencies: - abort-controller "^3.0.0" extend "^3.0.2" - https-proxy-agent "^5.0.0" + https-proxy-agent "^7.0.1" is-stream "^2.0.0" - node-fetch "^2.6.7" + node-fetch "^2.6.9" + uuid "^9.0.1" -gaxios@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-5.0.0.tgz#df11e5d0a45831dd39eb5fbbba0d6a6b09815e70" - integrity sha512-VD/yc5ln6XU8Ch1hyYY6kRMBE0Yc2np3fPyeJeYHhrPs1i8rgnsApPMWyrugkl7LLoSqpOJVBWlQIa87OAvt8Q== +gcp-metadata@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-6.1.0.tgz#9b0dd2b2445258e7597f2024332d20611cbd6b8c" + integrity sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg== dependencies: - abort-controller "^3.0.0" - extend "^3.0.2" - https-proxy-agent "^5.0.0" - is-stream "^2.0.0" - node-fetch "^2.6.7" - -gcp-metadata@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-5.0.0.tgz#a00f999f60a4461401e7c515f8a3267cfb401ee7" - integrity sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA== - dependencies: - gaxios "^5.0.0" + gaxios "^6.0.0" json-bigint "^1.0.0" get-caller-file@^2.0.5: @@ -2178,46 +2229,35 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -google-auth-library@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-8.0.2.tgz#5fa0f2d3795c3e4019d2bb315ade4454cc9c30b5" - integrity sha512-HoG+nWFAThLovKpvcbYzxgn+nBJPTfAwtq0GxPN821nOO+21+8oP7MoEHfd1sbDulUFFGfcjJr2CnJ4YssHcyg== +google-auth-library@^9.3.0: + version "9.7.0" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-9.7.0.tgz#dd99a08e2e3f70778de8be4ed8556460e237550a" + integrity sha512-I/AvzBiUXDzLOy4iIZ2W+Zq33W4lcukQv1nl7C8WUA6SQwyQwUwu3waNmWNAvzds//FG8SZ+DnKnW/2k6mQS8A== dependencies: - arrify "^2.0.0" base64-js "^1.3.0" ecdsa-sig-formatter "^1.0.11" - fast-text-encoding "^1.0.0" - gaxios "^5.0.0" - gcp-metadata "^5.0.0" - gtoken "^5.3.2" + gaxios "^6.1.1" + gcp-metadata "^6.1.0" + gtoken "^7.0.0" jws "^4.0.0" - lru-cache "^6.0.0" -google-gax@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-3.1.0.tgz#a6fea06bfd0157969ddc0a6d04c47980978ead02" - integrity sha512-OHKVHZtaYHwxrjiI4BH+IzRWH2Q75sZg+q0/RatxLicbeeCCIug99+NKQiE39B5rLnGENtcbUjmXiqJHbSrkSg== +google-gax@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-4.3.2.tgz#417cbee97f2e68d78f641af19c0f15234c0dbd9c" + integrity sha512-2mw7qgei2LPdtGrmd1zvxQviOcduTnsvAWYzCxhOWXK4IQKmQztHnDQwD0ApB690fBQJemFKSU7DnceAy3RLzw== dependencies: - "@grpc/grpc-js" "~1.6.0" - "@grpc/proto-loader" "^0.6.12" + "@grpc/grpc-js" "~1.10.0" + "@grpc/proto-loader" "^0.7.0" "@types/long" "^4.0.0" abort-controller "^3.0.0" duplexify "^4.0.0" - fast-text-encoding "^1.0.3" - google-auth-library "^8.0.2" - is-stream-ended "^0.1.4" + google-auth-library "^9.3.0" node-fetch "^2.6.1" object-hash "^3.0.0" - proto3-json-serializer "^1.0.0" - protobufjs "6.11.3" - retry-request "^5.0.0" - -google-p12-pem@^3.1.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-3.1.4.tgz#123f7b40da204de4ed1fbf2fd5be12c047fc8b3b" - integrity sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg== - dependencies: - node-forge "^1.3.1" + proto3-json-serializer "^2.0.0" + protobufjs "7.2.6" + retry-request "^7.0.0" + uuid "^9.0.1" got@^9.6.0: version "9.6.0" @@ -2241,13 +2281,12 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -gtoken@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.3.2.tgz#deb7dc876abe002178e0515e383382ea9446d58f" - integrity sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ== +gtoken@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26" + integrity sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw== dependencies: - gaxios "^4.0.0" - google-p12-pem "^3.1.3" + gaxios "^6.0.0" jws "^4.0.0" has-bigints@^1.0.1, has-bigints@^1.0.2: @@ -2301,6 +2340,11 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +heap-js@^2.2.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/heap-js/-/heap-js-2.5.0.tgz#487e268b1733b187ca04eccf52f8387be92b46cb" + integrity sha512-kUGoI3p7u6B41z/dp33G6OaL7J4DRqRYwVmeIlwLClx7yaaAy7hoDExnuejTKtuDwfcatGmddHDEOjf6EyIxtQ== + http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -2317,6 +2361,15 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + https-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" @@ -2325,6 +2378,14 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +https-proxy-agent@^7.0.1: + version "7.0.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168" + integrity sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg== + dependencies: + agent-base "^7.0.2" + debug "4" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -2728,6 +2789,11 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +long@^5.0.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + long@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/long/-/long-5.2.0.tgz#2696dadf4b4da2ce3f6f6b89186085d94d52fd61" @@ -2868,16 +2934,16 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +ms@2.1.2, ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + ms@2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - multistream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/multistream/-/multistream-4.1.0.tgz#7bf00dfd119556fbc153cff3de4c6d477909f5a8" @@ -2903,18 +2969,13 @@ node-abi@^2.21.0: dependencies: semver "^5.4.1" -node-fetch@^2.6.1, node-fetch@^2.6.6, node-fetch@^2.6.7: +node-fetch@^2.6.1, node-fetch@^2.6.6, node-fetch@^2.6.7, node-fetch@^2.6.9: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" -node-forge@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== - nodemon@^2.0.16: version "2.0.16" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.16.tgz#d71b31bfdb226c25de34afea53486c8ef225fdef" @@ -3164,17 +3225,17 @@ progress@^2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -proto3-json-serializer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/proto3-json-serializer/-/proto3-json-serializer-1.0.1.tgz#5d2b0b3c85568edb62984c94ce2e726985de44be" - integrity sha512-jtnGKL8EE1mTOl2qgMZJQXbS22dlb2K07i4b5vp8yMGMJ6mFSgBg4Ossu/g9NCuamdYXHjp3XxyWw4tJ0G/uKw== +proto3-json-serializer@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/proto3-json-serializer/-/proto3-json-serializer-2.0.1.tgz#da0b510f6d6e584b1b5c271f045c26728abe71e0" + integrity sha512-8awBvjO+FwkMd6gNoGFZyqkHZXCFd54CIYTb6De7dPaufGJ2XNW+QUNqbMr8MaAocMdb+KpsD4rxEOaTBDCffA== dependencies: - protobufjs "^6.11.3" + protobufjs "^7.2.5" -protobufjs@6.11.3, protobufjs@^6.11.3: - version "6.11.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" - integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== +protobufjs@7.2.6, protobufjs@^7.2.4, protobufjs@^7.2.5: + version "7.2.6" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.6.tgz#4a0ccd79eb292717aacf07530a07e0ed20278215" + integrity sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -3186,9 +3247,8 @@ protobufjs@6.11.3, protobufjs@^6.11.3: "@protobufjs/path" "^1.1.2" "@protobufjs/pool" "^1.1.0" "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.1" "@types/node" ">=13.7.0" - long "^4.0.0" + long "^5.0.0" proxy-addr@~2.0.7: version "2.0.7" @@ -3363,13 +3423,14 @@ responselike@^1.0.2: dependencies: lowercase-keys "^1.0.0" -retry-request@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-5.0.0.tgz#886ff8ec0e77fffbe66a4d5e90fd8f6646b6eae4" - integrity sha512-vBZdBxUordje9253imlmGtppC5gdcwZmNz7JnU2ui+KKFPk25keR+0c020AVV20oesYxIFOI0Kh3HE88/59ieg== +retry-request@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-7.0.2.tgz#60bf48cfb424ec01b03fca6665dee91d06dd95f3" + integrity sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w== dependencies: - debug "^4.1.1" + "@types/request" "^2.48.8" extend "^3.0.2" + teeny-request "^9.0.0" reusify@^1.0.4: version "1.0.4" @@ -3547,6 +3608,13 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +stream-events@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" + integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg== + dependencies: + stubs "^3.0.0" + stream-meter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/stream-meter/-/stream-meter-1.0.4.tgz#52af95aa5ea760a2491716704dbff90f73afdd1d" @@ -3594,7 +3662,7 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string-width@^4.2.2: +string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -3680,6 +3748,11 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +stubs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" + integrity sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw== + supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -3720,6 +3793,17 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" +teeny-request@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-9.0.0.tgz#18140de2eb6595771b1b02203312dfad79a4716d" + integrity sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g== + dependencies: + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + node-fetch "^2.6.9" + stream-events "^1.0.5" + uuid "^9.0.0" + text-hex@1.0.x: version "1.0.0" resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" @@ -3973,6 +4057,11 @@ uuid@^8.3.0, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0, uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -4138,6 +4227,11 @@ yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" @@ -4151,6 +4245,19 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" diff --git a/pom.xml b/pom.xml index 97b0aef297..f593de1138 100755 --- a/pom.xml +++ b/pom.xml @@ -75,7 +75,7 @@ 2.3.30 1.6.2 5.5.0 - 3.8.1 + 3.9.2 3.21.9 1.42.1 1.1.5 @@ -94,7 +94,7 @@ 1.18.2 1.69 2.0.1 - 42.5.0 + 42.7.3 org/thingsboard/server/gen/**/*, org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java index fd63ace9c4..d108a6981d 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java @@ -38,7 +38,7 @@ public interface NotificationCenter { void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId); - void markAllNotificationsAsRead(TenantId tenantId, UserId recipientId); + void markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId); void deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/FirebaseService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/FirebaseService.java index 28aa4c96af..ef10d8cecc 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/FirebaseService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/FirebaseService.java @@ -21,6 +21,6 @@ import java.util.Map; public interface FirebaseService { - void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body, Map data) throws Exception; + void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body, Map data, Integer badge) throws Exception; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java index 958957a3e2..40d899412d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java @@ -87,7 +87,7 @@ public class CertPemCredentials implements ClientCredentials { private KeyManagerFactory createAndInitKeyManagerFactory() throws Exception { KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(loadKeyStore(), password.toCharArray()); + kmf.init(loadKeyStore(), SslUtil.getPassword(password)); return kmf; } @@ -107,7 +107,7 @@ public class CertPemCredentials implements ClientCredentials { CertPath certPath = factory.generateCertPath(certificates); List path = certPath.getCertificates(); Certificate[] x509Certificates = path.toArray(new Certificate[0]); - keyStore.setKeyEntry(PRIVATE_KEY_ALIAS, privateKey, password.toCharArray(), x509Certificates); + keyStore.setKeyEntry(PRIVATE_KEY_ALIAS, privateKey, SslUtil.getPassword(password), x509Certificates); } return keyStore; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java index 08a1776ba3..1d2f665376 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import javax.annotation.Nullable; +import java.util.Objects; @Slf4j @RuleNode( @@ -57,7 +58,7 @@ public class TbCheckAlarmStatusNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { try { Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class); - + Objects.requireNonNull(alarm, "alarm is null"); ListenableFuture latest = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), alarm.getId()); Futures.addCallback(latest, new FutureCallback<>() { @@ -78,7 +79,11 @@ public class TbCheckAlarmStatusNode implements TbNode { } }, ctx.getDbCallbackExecutor()); } catch (Exception e) { - log.error("Failed to parse alarm: [{}]", msg.getData()); + if (e instanceof IllegalArgumentException || e instanceof NullPointerException) { + log.debug("[{}][{}] Failed to parse alarm: [{}] error [{}]", ctx.getTenantId(), ctx.getRuleChainName(), msg.getData(), e.getMessage()); + } else { + log.error("[{}][{}] Failed to parse alarm: [{}]", ctx.getTenantId(), ctx.getRuleChainName(), msg.getData(), e); + } throw new TbNodeException(e); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java index c79f962818..5b71db5dfb 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java @@ -38,8 +38,10 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.willReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -158,6 +160,19 @@ class TbCheckAlarmStatusNodeTest { assertThat(value).isInstanceOf(TbNodeException.class).hasMessage("No such alarm found."); } + @Test + void givenUnparseableAlarm_whenOnMsg_then_Failure() { + String msgData = "{\"Number\":1113718,\"id\":8.1}"; + TbMsg msg = getTbMsg(msgData); + willReturn("Default Rule Chain").given(ctx).getRuleChainName(); + + assertThatThrownBy(() -> node.onMsg(ctx, msg)) + .as("onMsg") + .isInstanceOf(TbNodeException.class) + .hasCauseInstanceOf(IllegalArgumentException.class) + .hasMessage("java.lang.IllegalArgumentException: The given string value cannot be transformed to Json object: {\"Number\":1113718,\"id\":8.1}"); + } + private TbMsg getTbMsg(String msgData) { return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, msgData); } diff --git a/rule-engine/rule-engine-components/src/test/resources/logback-test.xml b/rule-engine/rule-engine-components/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..0695fe1e1a --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/ui-ngx/.eslintrc.json b/ui-ngx/.eslintrc.json index cf9f0e8238..764733e4d7 100644 --- a/ui-ngx/.eslintrc.json +++ b/ui-ngx/.eslintrc.json @@ -51,7 +51,8 @@ "import/order": "off", "@typescript-eslint/member-ordering": "off", "no-underscore-dangle": "off", - "@typescript-eslint/naming-convention": "off" + "@typescript-eslint/naming-convention": "off", + "jsdoc/newline-after-description": 0 } }, { diff --git a/ui-ngx/package.json b/ui-ngx/package.json index a74a1f2953..46cd5dd3cd 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -30,7 +30,7 @@ "@date-io/date-fns": "1.3.7", "@flowjs/flow.js": "^2.14.1", "@flowjs/ngx-flow": "~0.6.0", - "@geoman-io/leaflet-geoman-free": "^2.13.0", + "@geoman-io/leaflet-geoman-free": "2.14.2", "@iplab/ngx-color-picker": "^15.0.2", "@juggle/resize-observer": "^3.4.0", "@mat-datetimepicker/core": "~11.0.3", @@ -67,11 +67,11 @@ "jstree": "^3.3.15", "jstree-bootstrap-theme": "^1.0.1", "jszip": "^3.10.1", - "leaflet": "~1.8.0", - "leaflet-polylinedecorator": "^1.6.0", - "leaflet-providers": "^1.13.0", - "leaflet.gridlayer.googlemutant": "^0.13.5", - "leaflet.markercluster": "^1.5.3", + "leaflet": "1.8.0", + "leaflet-polylinedecorator": "1.6.0", + "leaflet-providers": "1.13.0", + "leaflet.gridlayer.googlemutant": "0.14.1", + "leaflet.markercluster": "1.5.3", "libphonenumber-js": "^1.10.4", "marked": "^4.0.17", "moment": "^2.29.4", @@ -130,11 +130,11 @@ "@types/jasminewd2": "^2.0.10", "@types/jquery": "^3.5.16", "@types/js-beautify": "^1.13.3", - "@types/leaflet": "~1.8.0", - "@types/leaflet-polylinedecorator": "^1.6.1", - "@types/leaflet-providers": "^1.2.1", - "@types/leaflet.gridlayer.googlemutant": "^0.4.6", - "@types/leaflet.markercluster": "^1.5.1", + "@types/leaflet": "1.8.0", + "@types/leaflet-polylinedecorator": "1.6.4", + "@types/leaflet-providers": "1.2.4", + "@types/leaflet.gridlayer.googlemutant": "0.4.9", + "@types/leaflet.markercluster": "1.5.4", "@types/lodash": "^4.14.192", "@types/marked": "^4.0.8", "@types/node": "~18.15.11", diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 462825087e..bd9005c497 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -139,8 +139,9 @@ export class TbTimeSeriesChart { this.settings = mergeDeep({} as TimeSeriesChartSettings, timeSeriesChartDefaultSettings, this.inputSettings as TimeSeriesChartSettings); - const dashboardPageElement = this.ctx.$containerParent.parents('.tb-dashboard-page'); - this.darkMode = this.settings.darkMode || dashboardPageElement.hasClass('dark'); + const $dashboardPageElement = this.ctx.$containerParent.parents('.tb-dashboard-page'); + const dashboardPageElement = $dashboardPageElement.length ? $($dashboardPageElement[$dashboardPageElement.length-1]) : null; + this.darkMode = this.settings.darkMode || dashboardPageElement?.hasClass('dark'); this.setupYAxes(); this.setupData(); this.setupThresholds(); @@ -154,15 +155,17 @@ export class TbTimeSeriesChart { }); this.shapeResize$.observe(this.chartElement); } - this.darkModeObserver = new MutationObserver(mutations => { - for(let mutation of mutations) { - if (mutation.type === 'attributes' && mutation.attributeName === 'class') { - const darkMode = dashboardPageElement.hasClass('dark'); - this.setDarkMode(darkMode); + if (dashboardPageElement) { + this.darkModeObserver = new MutationObserver(mutations => { + for (const mutation of mutations) { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + const darkMode = dashboardPageElement.hasClass('dark'); + this.setDarkMode(darkMode); + } } - } - }); - this.darkModeObserver.observe(dashboardPageElement[0], { attributes: true }); + }); + this.darkModeObserver.observe(dashboardPageElement[0], {attributes: true}); + } } public update(): void { @@ -269,7 +272,7 @@ export class TbTimeSeriesChart { } this.yMinSubject.complete(); this.yMaxSubject.complete(); - this.darkModeObserver.disconnect(); + this.darkModeObserver?.disconnect(); } public resize(): void { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.scss index 3425065ca2..3f66b67943 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.scss @@ -63,7 +63,9 @@ height: 18px; background: #305680; mask-image: url(/assets/copy-code-icon.svg); + -webkit-mask-image: url(/assets/copy-code-icon.svg); mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss index 444417fb09..4bef541ad2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss @@ -65,6 +65,8 @@ .tb-battery-level-box { display: flex; align-items: center; + min-height: 0; + max-height: 100%; .tb-battery-level-rectangle { width: 100%; height: 100%; @@ -73,8 +75,11 @@ position: absolute; inset: 0; mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; mask-size: cover; + -webkit-mask-size: cover; mask-position: center; + -webkit-mask-position: center; } .tb-battery-level-container { position: absolute; @@ -95,6 +100,7 @@ &.vertical { .tb-battery-level-shape { mask-image: url(/assets/widget/battery-level/battery-shape-vertical.svg); + -webkit-mask-image: url(/assets/widget/battery-level/battery-shape-vertical.svg); } .tb-battery-level-container { flex-direction: column-reverse; @@ -122,6 +128,7 @@ &.horizontal { .tb-battery-level-shape { mask-image: url(/assets/widget/battery-level/battery-shape-horizontal.svg); + -webkit-mask-image: url(/assets/widget/battery-level/battery-shape-horizontal.svg); } .tb-battery-level-container { inset: 6.25% 8.85% 6.25% 3.54%; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts index 5251a0c602..0cc6d266b0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts @@ -307,7 +307,7 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView const newWidth = height * horizontalBatteryDimensions.shapeAspectRatio; this.renderer.setStyle(this.batteryLevelRectangle.nativeElement, 'width', newWidth + 'px'); } else { - this.renderer.setStyle(this.batteryLevelRectangle.nativeElement, 'width', null); + this.renderer.setStyle(this.batteryLevelRectangle.nativeElement, 'width', width + 'px'); } if (this.batteryLevelValue) { const ratios = horizontalBatteryDimensions.heightRatio; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts index 51a427d23b..656ba75fa1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts @@ -444,7 +444,7 @@ class InnerShadowCircle { add.x('-50%').y('-50%').width('200%').height('200%'); let effect: Effect = add.componentTransfer(components => { components.funcA({ type: 'table', tableValues: '1 0' }); - }).in(add.$fill); + }); effect = effect.gaussianBlur(this.blur, this.blur).attr({stdDeviation: this.blur}); this.blurEffect = effect; effect = effect.offset(this.dx, this.dy); @@ -454,7 +454,7 @@ class InnerShadowCircle { effect = effect.composite(this.offsetEffect, 'in'); effect.composite(add.$sourceAlpha, 'in'); add.merge(m => { - m.mergeNode(add.$fill); + m.mergeNode(); m.mergeNode(); }); }); @@ -538,14 +538,14 @@ class DefaultPowerButtonShape extends PowerButtonShape { this.pressedTimeline.finish(); const pressedScale = 0.75; powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); - powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale}); + powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale, origin: {x: cx, y: cy}}); this.pressedShadow.animate(6, 0.6); } protected onPressEnd() { this.pressedTimeline.finish(); powerButtonAnimation(this.centerGroup).transform({scale: 1}); - powerButtonAnimation(this.onLabelShape).transform({scale: 1}); + powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}}); this.pressedShadow.animateRestore(); } @@ -602,14 +602,14 @@ class SimplifiedPowerButtonShape extends PowerButtonShape { this.pressedTimeline.finish(); const pressedScale = 0.75; powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); - powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale}); + powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale, origin: {x: cx, y: cy}}); this.pressedShadow.animate(6, 0.6); } protected onPressEnd() { this.pressedTimeline.finish(); powerButtonAnimation(this.centerGroup).transform({scale: 1}); - powerButtonAnimation(this.onLabelShape).transform({scale: 1}); + powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}}); this.pressedShadow.animateRestore(); } } @@ -674,7 +674,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape { const pressedScale = 0.75; powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); powerButtonAnimation(this.onCenterGroup).transform({scale: 0.98}); - powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98}); + powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98, origin: {x: cx, y: cy}}); this.pressedShadow.animate(6, 0.6); } @@ -682,7 +682,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape { this.pressedTimeline.finish(); powerButtonAnimation(this.centerGroup).transform({scale: 1}); powerButtonAnimation(this.onCenterGroup).transform({scale: 1}); - powerButtonAnimation(this.onLabelShape).transform({scale: 1}); + powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}}); this.pressedShadow.animateRestore(); } } @@ -774,14 +774,14 @@ class DefaultVolumePowerButtonShape extends PowerButtonShape { this.innerShadow.show(); const pressedScale = 0.75; powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); - powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale}); + powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale, origin: {x: cx, y: cy}}); this.innerShadow.animate(6, 0.6); } protected onPressEnd() { this.pressedTimeline.finish(); powerButtonAnimation(this.centerGroup).transform({scale: 1}); - powerButtonAnimation(this.onLabelShape).transform({scale: 1}); + powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}}); this.innerShadow.animateRestore().after(() => { if (this.disabled) { this.innerShadow.hide(); @@ -849,14 +849,14 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape { this.backgroundShape.removeClass('tb-shadow'); } powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); - powerButtonAnimation(this.onCenterGroup).transform({scale: pressedScale}); + powerButtonAnimation(this.onCenterGroup).transform({scale: pressedScale, origin: {x: cx, y: cy}}); this.pressedShadow.animate(8, 0.4); } protected onPressEnd() { this.pressedTimeline.finish(); powerButtonAnimation(this.centerGroup).transform({scale: 1}); - powerButtonAnimation(this.onCenterGroup).transform({scale: 1}); + powerButtonAnimation(this.onCenterGroup).transform({scale: 1, origin: {x: cx, y: cy}}); this.pressedShadow.animateRestore().after(() => { if (!this.value) { this.backgroundShape.addClass('tb-shadow'); @@ -938,7 +938,7 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape { const pressedScale = 0.75; powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); powerButtonAnimation(this.onCenterGroup).transform({scale: 0.98}); - powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98}); + powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98, origin: {x: cx, y: cy}}); this.pressedShadow.animate(6, 0.6); } @@ -946,7 +946,7 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape { this.pressedTimeline.finish(); powerButtonAnimation(this.centerGroup).transform({scale: 1}); powerButtonAnimation(this.onCenterGroup).transform({scale: 1}); - powerButtonAnimation(this.onLabelShape).transform({scale: 1}); + powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}}); this.pressedShadow.animateRestore(); } diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss index f7ed839541..ef5a1c0ff4 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss @@ -155,7 +155,9 @@ height: 18px; background: #305680; mask-image: url(/assets/copy-code-icon.svg); + -webkit-mask-image: url(/assets/copy-code-icon.svg); mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; } } } diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.scss index 4ff80e2c21..b28b28863e 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.scss @@ -92,7 +92,9 @@ height: 18px; background: $tb-primary-color; mask-image: url(/assets/copy-code-icon.svg); + -webkit-mask-image: url(/assets/copy-code-icon.svg); mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; } } &.multiline { diff --git a/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.html b/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.html index 4a60b29ba9..ea6a4cc603 100644 --- a/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.html +++ b/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.html @@ -85,12 +85,11 @@
- diff --git a/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.ts b/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.ts index 260a7667f8..a1183acf88 100644 --- a/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.ts +++ b/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core'; +import { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -24,10 +24,13 @@ import { DndDropEvent } from 'ngx-drag-drop'; import { isUndefined } from '@core/utils'; import { coerceBoolean } from '@shared/decorators/coercion'; import { ImageLinkType } from '@shared/components/image/gallery-image-input.component'; -import { TbPopoverService } from '@shared/components/popover.service'; -import { MatButton } from '@angular/material/button'; -import { ImageGalleryComponent } from '@shared/components/image/image-gallery.component'; -import { prependTbImagePrefixToUrls, removeTbImagePrefixFromUrls } from '@shared/models/resource.models'; +import { + ImageResourceInfo, + prependTbImagePrefixToUrls, + removeTbImagePrefixFromUrls +} from '@shared/models/resource.models'; +import { MatDialog } from '@angular/material/dialog'; +import { ImageGalleryDialogComponent } from '@shared/components/image/image-gallery-dialog.component'; @Component({ selector: 'tb-multiple-gallery-image-input', @@ -66,10 +69,8 @@ export class MultipleGalleryImageInputComponent extends PageComponent implements private propagateChange = null; constructor(protected store: Store, - private cd: ChangeDetectorRef, - private renderer: Renderer2, - private viewContainerRef: ViewContainerRef, - private popoverService: TbPopoverService) { + private dialog: MatDialog, + private cd: ChangeDetectorRef) { super(store); } @@ -130,31 +131,21 @@ export class MultipleGalleryImageInputComponent extends PageComponent implements this.updateModel(); } - toggleGallery($event: Event, browseGalleryButton: MatButton) { + toggleGallery($event: Event) { if ($event) { $event.stopPropagation(); } - const trigger = browseGalleryButton._elementRef.nativeElement; - if (this.popoverService.hasPopover(trigger)) { - this.popoverService.hidePopover(trigger); - } else { - const ctx: any = { - pageMode: false, - popoverMode: true, - mode: 'grid', - selectionMode: true - }; - const imageGalleryPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ImageGalleryComponent, 'top', true, null, - ctx, - {}, - {}, {}, true); - imageGalleryPopover.tbComponentRef.instance.imageSelected.subscribe((image) => { - imageGalleryPopover.hide(); + this.dialog.open(ImageGalleryDialogComponent, { + autoFocus: false, + disableClose: false, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'] + }).afterClosed().subscribe((image) => { + if (image) { this.imageUrls.push(image.link); this.updateModel(); - }); - } + } + }); } imageDragStart(index: number) { diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts index 8775b0a6ed..8ed6fb2034 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts @@ -21,6 +21,7 @@ import { coerceNumberProperty } from '@angular/cdk/coercion'; import { SubscriptSizing } from '@angular/material/form-field'; import { coerceBoolean } from '@shared/decorators/coercion'; import { Interval, IntervalMath, TimeInterval } from '@shared/models/time/time.models'; +import { isDefined } from '@core/utils'; @Component({ selector: 'tb-timeinterval', @@ -90,14 +91,17 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { secs = 0; interval: Interval = 0; - modelValue: Interval; - - advanced = false; - rendered = false; - intervals: Array; - private propagateChange = (_: any) => {}; + advanced = false; + + private modelValue: Interval; + private rendered = false; + private propagateChangeValue: any; + + private propagateChange = (value: any) => { + this.propagateChangeValue = value; + }; constructor(private timeService: TimeService) { } @@ -108,6 +112,9 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { registerOnChange(fn: any): void { this.propagateChange = fn; + if (isDefined(this.propagateChangeValue)) { + this.propagateChange(this.propagateChangeValue); + } } registerOnTouched(fn: any): void { @@ -132,7 +139,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { } } - setInterval(interval: Interval) { + private setInterval(interval: Interval) { if (!this.advanced) { this.interval = interval; } @@ -143,7 +150,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { this.secs = intervalSeconds % 60; } - boundInterval(updateToPreferred = false) { + private boundInterval(updateToPreferred = false) { const min = this.timeService.boundMinInterval(this.minValue); const max = this.timeService.boundMaxInterval(this.maxValue); this.intervals = this.timeService.getIntervals(this.minValue, this.maxValue, this.useCalendarIntervals); @@ -165,7 +172,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { } } - updateView(updateToPreferred = false) { + private updateView(updateToPreferred = false) { if (!this.rendered) { return; } @@ -187,7 +194,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { this.boundInterval(updateToPreferred); } - calculateIntervalMs(): number { + private calculateIntervalMs(): number { return (this.days * 86400 + this.hours * 3600 + this.mins * 60 + @@ -232,7 +239,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { } } - onSecsChange() { + private onSecsChange() { if (typeof this.secs === 'undefined') { return; } @@ -252,7 +259,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { this.updateView(); } - onMinsChange() { + private onMinsChange() { if (typeof this.mins === 'undefined') { return; } @@ -272,7 +279,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { this.updateView(); } - onHoursChange() { + private onHoursChange() { if (typeof this.hours === 'undefined') { return; } @@ -292,7 +299,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { this.updateView(); } - onDaysChange() { + private onDaysChange() { if (typeof this.days === 'undefined') { return; } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 0c0dd4294c..857d2c7cb7 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -20,7 +20,6 @@ import { AggregationType, DAY, HistoryWindowType, - QuickTimeInterval, quickTimeIntervalPeriod, RealtimeWindowType, Timewindow, diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index e2390c795d..f4d3cdb398 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -588,9 +588,13 @@ bottom: 0; background: $tb-primary-color; mask-image: url(/assets/home/no_data_folder_bg.svg); + -webkit-mask-image: url(/assets/home/no_data_folder_bg.svg); mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; mask-size: contain; + -webkit-mask-size: contain; mask-position: center; + -webkit-mask-position: center; } } diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index ca5c179ad5..33362767bd 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -1582,10 +1582,10 @@ dependencies: tslib "^2.2.0" -"@geoman-io/leaflet-geoman-free@^2.13.0": - version "2.16.0" - resolved "https://registry.yarnpkg.com/@geoman-io/leaflet-geoman-free/-/leaflet-geoman-free-2.16.0.tgz#c8a92fcc2cdd5770ebf43f8ef9f03cc8ef842359" - integrity sha512-BnKAAoTXraWVFfhX/0gT/iBgAz1BPfpbdQ9dJamEFI4lIku9UNXXluu/E0k7YkZETq0tENX2GPnKLB4p+VgrSw== +"@geoman-io/leaflet-geoman-free@2.14.2": + version "2.14.2" + resolved "https://registry.yarnpkg.com/@geoman-io/leaflet-geoman-free/-/leaflet-geoman-free-2.14.2.tgz#c84c2115c263f34d11dc0b43859551639fe3d56b" + integrity sha512-6lIyG8RvSVdFjVjiQgBPyNASjymSyqzsiUeBW0pA+q41lB5fAg4SDC6SfJvWdEyDHa81Jb5FWjUkCc9O+u0gbg== dependencies: "@turf/boolean-contains" "^6.5.0" "@turf/kinks" "^6.5.0" @@ -3079,28 +3079,28 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/leaflet-polylinedecorator@^1.6.1": +"@types/leaflet-polylinedecorator@1.6.4": version "1.6.4" resolved "https://registry.yarnpkg.com/@types/leaflet-polylinedecorator/-/leaflet-polylinedecorator-1.6.4.tgz#2270b84585bd28bbf1fef237be8480196c44ac06" integrity sha512-meH/jC58Drt/9aqLhuzMCUQjHgMTud+UuQBK5gT6E8M6OfM/rftz/VgvdxOCCAQC9isrp4pqahDgEswesb5X3A== dependencies: "@types/leaflet" "*" -"@types/leaflet-providers@^1.2.1": +"@types/leaflet-providers@1.2.4": version "1.2.4" resolved "https://registry.yarnpkg.com/@types/leaflet-providers/-/leaflet-providers-1.2.4.tgz#284e8a197d4c2dbfeda436842e03b2adb502be16" integrity sha512-4wYEpreixp+G5t510s202eQ5eubOmxHevIfCNrzUZbzp50XQsC9lSetX7MnPl3ANRJnUBbCWOzZ9EQFiH0Jm+g== dependencies: "@types/leaflet" "*" -"@types/leaflet.gridlayer.googlemutant@^0.4.6": +"@types/leaflet.gridlayer.googlemutant@0.4.9": version "0.4.9" resolved "https://registry.yarnpkg.com/@types/leaflet.gridlayer.googlemutant/-/leaflet.gridlayer.googlemutant-0.4.9.tgz#9090ddf4578ce631d22b8aafc5ed12525b4fbf90" integrity sha512-u/5Avs1KKkeABRDixGbGp2ldFs67+fYIATpkvvFgN+LQPNfq7dFd5adkksshiQBfYJ4SaQg1VJgN2dNirIC0aQ== dependencies: "@types/leaflet" "*" -"@types/leaflet.markercluster@^1.5.1": +"@types/leaflet.markercluster@1.5.4": version "1.5.4" resolved "https://registry.yarnpkg.com/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.4.tgz#2ab43417cf3f6a42d0f1baf4e1c8f659cf1dc3a1" integrity sha512-tfMP8J62+wfsVLDLGh5Zh1JZxijCaBmVsMAX78MkLPwvPitmZZtSin5aWOVRhZrCS+pEOZwNzexbfWXlY+7yjg== @@ -3114,7 +3114,7 @@ dependencies: "@types/geojson" "*" -"@types/leaflet@~1.8.0": +"@types/leaflet@1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.8.0.tgz#dc92d3e868fb6d5067b4b59fa08cd4441f84fabe" integrity sha512-+sXFmiJTFdhaXXIGFlV5re9AdqtAODoXbGAvxx02e5SHXL3ir7ClP5J7pahO8VmzKY3dth4RUS1nf2BTT+DW1A== @@ -7750,14 +7750,14 @@ lcov-parse@1.0.0: resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-1.0.0.tgz#eb0d46b54111ebc561acb4c408ef9363bdc8f7e0" integrity sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ== -leaflet-polylinedecorator@^1.6.0: +leaflet-polylinedecorator@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/leaflet-polylinedecorator/-/leaflet-polylinedecorator-1.6.0.tgz#9ef79fd1b5302d67b72efe959a8ecd2553f27266" integrity sha512-kn3krmZRetgvN0wjhgYL8kvyLS0tUogAl0vtHuXQnwlYNjbl7aLQpkoFUo8UB8gVZoB0dhI4Tb55VdTJAcYzzQ== dependencies: leaflet-rotatedmarker "^0.2.0" -leaflet-providers@^1.13.0: +leaflet-providers@1.13.0: version "1.13.0" resolved "https://registry.yarnpkg.com/leaflet-providers/-/leaflet-providers-1.13.0.tgz#10c843a23d5823a65096d40ad53f27029e13434b" integrity sha512-f/sN5wdgBbVA2jcCYzScIfYNxKdn2wBJP9bu+5cRX9Xj6g8Bt1G9Sr8WgJAt/ckIFIc3LVVxCBNFpSCfTuUElg== @@ -7767,17 +7767,17 @@ leaflet-rotatedmarker@^0.2.0: resolved "https://registry.yarnpkg.com/leaflet-rotatedmarker/-/leaflet-rotatedmarker-0.2.0.tgz#4467f49f98d1bfd56959bd9c6705203dd2601277" integrity sha512-yc97gxLXwbZa+Gk9VCcqI0CkvIBC9oNTTjFsHqq4EQvANrvaboib4UdeQLyTnEqDpaXHCqzwwVIDHtvz2mUiDg== -leaflet.gridlayer.googlemutant@^0.13.5: - version "0.13.5" - resolved "https://registry.yarnpkg.com/leaflet.gridlayer.googlemutant/-/leaflet.gridlayer.googlemutant-0.13.5.tgz#7182c26f479e726bff8b85a7ebf9d3f44dd3ea57" - integrity sha512-DHUEXpo1t0WZ9tpdLUHHkrTK7LldCXr/gqkV5E/4hHidDJB2srceLLCZj4PV/pl0jPdTkVBT+Lr8nL9EZDB5hw== +leaflet.gridlayer.googlemutant@0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/leaflet.gridlayer.googlemutant/-/leaflet.gridlayer.googlemutant-0.14.1.tgz#c282209aa1a39eb2f87d8aaa4e9894181c9e20a0" + integrity sha512-/OYxEjmgxO1U1KOhTzg+m8c0b95J0943LU8DXQmdJu/x2f+1Ur78rvEPO2QCS0cmwZ3m6FvE5I3zXnBzJNWRCA== -leaflet.markercluster@^1.5.3: +leaflet.markercluster@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz#9cdb52a4eab92671832e1ef9899669e80efc4056" integrity sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA== -leaflet@~1.8.0: +leaflet@1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.8.0.tgz#4615db4a22a304e8e692cae9270b983b38a2055e" integrity sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA==