Browse Source

Merge pull request #8334 from thingsboard/improvements/notification-system

Notification system improvements
pull/8343/head
Andrew Shvayka 3 years ago
committed by GitHub
parent
commit
e23a93d282
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      application/src/main/data/upgrade/3.4.4/schema_update.sql
  2. 6
      application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java
  3. 45
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java
  4. 116
      application/src/main/java/org/thingsboard/server/service/notification/rule/cache/DefaultNotificationRulesCache.java
  5. 19
      application/src/main/java/org/thingsboard/server/service/notification/rule/cache/NotificationRulesCache.java
  6. 17
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java
  7. 3
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  8. 10
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  9. 8
      application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java
  10. 7
      application/src/main/resources/thingsboard.yml
  11. 2
      common/cluster-api/src/main/proto/queue.proto
  12. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  13. 16
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java
  14. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionNotificationRuleTriggerConfig.java
  15. 2
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/NewPlatformVersionTrigger.java
  16. 27
      common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java
  17. 46
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java
  18. 9
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java
  19. 6
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java
  20. 43
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheKey.java
  21. 32
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCaffeineCache.java
  22. 35
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleRedisCache.java
  23. 4
      dao/src/main/resources/sql/schema-entities.sql
  24. 14
      ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html
  25. 20
      ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts
  26. 11
      ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts
  27. 11
      ui-ngx/src/app/shared/models/notification.models.ts
  28. 46
      ui-ngx/src/assets/help/en_US/notification/new_platform_version.md
  29. 5
      ui-ngx/src/assets/locale/locale.constant-en_US.json

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

@ -107,7 +107,7 @@ CREATE TABLE IF NOT EXISTS notification_template (
tenant_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
notification_type VARCHAR(50) NOT NULL,
configuration VARCHAR(10000) NOT NULL,
configuration VARCHAR(10000000) NOT NULL,
CONSTRAINT uq_notification_template_name UNIQUE (tenant_id, name)
);
CREATE INDEX IF NOT EXISTS idx_notification_template_tenant_id_created_time ON notification_template(tenant_id, created_time DESC);
@ -132,7 +132,7 @@ CREATE TABLE IF NOT EXISTS notification_request (
tenant_id UUID NOT NULL,
targets VARCHAR(10000) NOT NULL,
template_id UUID,
template VARCHAR(10000),
template VARCHAR(10000000),
info VARCHAR(1000),
additional_config VARCHAR(1000),
originator_entity_id UUID,

6
application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java

@ -67,7 +67,11 @@ public class NotificationRuleController extends BaseController {
throw new IllegalArgumentException("Trigger type " + triggerType + " is not available");
}
return doSaveAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::saveNotificationRule);
boolean created = notificationRule.getId() == null;
notificationRule = doSaveAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::saveNotificationRule);
tbClusterService.broadcastEntityStateChangeEvent(user.getTenantId(), notificationRule.getId(), created ?
ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
return notificationRule;
}
@GetMapping("/rule/{id}")

45
application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java

@ -35,12 +35,14 @@ import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.service.notification.rule.cache.NotificationRulesCache;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.service.executors.NotificationExecutorService;
import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor;
@ -60,8 +62,9 @@ import java.util.stream.Collectors;
@SuppressWarnings({"rawtypes", "unchecked"})
public class DefaultNotificationRuleProcessor implements NotificationRuleProcessor {
private final NotificationRuleService notificationRuleService;
private final NotificationRulesCache notificationRulesCache;
private final NotificationRequestService notificationRequestService;
private final PartitionService partitionService;
@Autowired @Lazy
private NotificationCenter notificationCenter;
private final NotificationExecutorService notificationExecutor;
@ -74,15 +77,19 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess
if (triggerType == null) return;
TenantId tenantId = triggerType.isTenantLevel() ? trigger.getTenantId() : TenantId.SYS_TENANT_ID;
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType);
for (NotificationRule rule : rules) {
notificationExecutor.submit(() -> {
try {
processNotificationRule(rule, trigger);
} catch (Throwable e) {
log.error("Failed to process notification rule {} for trigger type {} with trigger object {}", rule.getId(), rule.getTriggerType(), trigger, e);
}
});
try {
List<NotificationRule> rules = notificationRulesCache.get(tenantId, triggerType);
for (NotificationRule rule : rules) {
notificationExecutor.submit(() -> {
try {
processNotificationRule(rule, trigger);
} catch (Throwable e) {
log.error("Failed to process notification rule {} for trigger type {} with trigger object {}", rule.getId(), rule.getTriggerType(), trigger, e);
}
});
}
} catch (Throwable e) {
log.error("Failed to process notification rules for trigger: {}", trigger, e);
}
}
@ -169,12 +176,14 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess
TenantId tenantId = componentLifecycleMsg.getTenantId();
NotificationRuleId notificationRuleId = (NotificationRuleId) componentLifecycleMsg.getEntityId();
notificationExecutor.submit(() -> {
List<NotificationRequestId> scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId);
for (NotificationRequestId notificationRequestId : scheduledForRule) {
notificationCenter.deleteNotificationRequest(tenantId, notificationRequestId);
}
});
if (partitionService.isMyPartition(ServiceType.TB_CORE, tenantId, notificationRuleId)) {
notificationExecutor.submit(() -> {
List<NotificationRequestId> scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId);
for (NotificationRequestId notificationRequestId : scheduledForRule) {
notificationCenter.deleteNotificationRequest(tenantId, notificationRequestId);
}
});
}
}
@Autowired

116
application/src/main/java/org/thingsboard/server/service/notification/rule/cache/DefaultNotificationRulesCache.java

@ -0,0 +1,116 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule.cache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.notification.NotificationRuleService;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class DefaultNotificationRulesCache implements NotificationRulesCache {
private final NotificationRuleService notificationRuleService;
@Value("${cache.notificationRules.maxSize:1000}")
private int cacheMaxSize;
@Value("${cache.notificationRules.timeToLiveInMinutes:30}")
private int cacheValueTtl;
private Cache<CacheKey, List<NotificationRule>> cache;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
@PostConstruct
private void init() {
cache = Caffeine.newBuilder()
.maximumSize(cacheMaxSize)
.expireAfterAccess(cacheValueTtl, TimeUnit.MINUTES)
.build();
}
@EventListener(ComponentLifecycleMsg.class)
public void onComponentLifecycleEvent(ComponentLifecycleMsg event) {
switch (event.getEntityId().getEntityType()) {
case NOTIFICATION_RULE:
evict(event.getTenantId()); // TODO: evict by trigger type of the rule
break;
case TENANT:
if (event.getEvent() == ComponentLifecycleEvent.DELETED) {
lock.writeLock().lock(); // locking in case rules for tenant are fetched while evicting
try {
evict(event.getTenantId());
} finally {
lock.writeLock().unlock();
}
}
break;
}
}
@Override
public List<NotificationRule> get(TenantId tenantId, NotificationRuleTriggerType triggerType) {
lock.readLock().lock();
try {
log.trace("Retrieving notification rules of type {} for tenant {} from cache", triggerType, tenantId);
return cache.get(key(tenantId, triggerType), k -> {
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType);
log.trace("Fetched notification rules of type {} for tenant {} (count: {})", triggerType, tenantId, rules.size());
return !rules.isEmpty() ? rules : Collections.emptyList();
});
} finally {
lock.readLock().unlock();
}
}
private void evict(TenantId tenantId) {
cache.invalidateAll(Arrays.stream(NotificationRuleTriggerType.values())
.map(triggerType -> key(tenantId, triggerType))
.collect(Collectors.toList()));
log.trace("Evicted all notification rules for tenant {} from cache", tenantId);
}
private static CacheKey key(TenantId tenantId, NotificationRuleTriggerType triggerType) {
return new CacheKey(tenantId, triggerType);
}
@Data
private static class CacheKey {
private final TenantId tenantId;
private final NotificationRuleTriggerType triggerType;
}
}

19
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheValue.java → application/src/main/java/org/thingsboard/server/service/notification/rule/cache/NotificationRulesCache.java

@ -13,25 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
package org.thingsboard.server.service.notification.rule.cache;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import java.io.Serializable;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class NotificationRuleCacheValue implements Serializable {
public interface NotificationRulesCache {
private static final long serialVersionUID = 9503216785105415L;
private List<NotificationRule> notificationRules;
List<NotificationRule> get(TenantId tenantId, NotificationRuleTriggerType triggerType);
}

17
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java

@ -15,16 +15,15 @@
*/
package org.thingsboard.server.service.notification.rule.trigger;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.discovery.PartitionService;
@ -36,17 +35,21 @@ public class NewPlatformVersionTriggerProcessor implements NotificationRuleTrigg
@Override
public boolean matchesFilter(NewPlatformVersionTrigger trigger, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) {
// todo: don't send repetitive notification after platform restart?
if (!partitionService.resolve(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID).isMyPartition()) {
if (!partitionService.isMyPartition(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID)) {
return false;
}
return trigger.getMessage().isUpdateAvailable();
return trigger.getUpdateInfo().isUpdateAvailable();
}
@Override
public RuleOriginatedNotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger) {
UpdateMessage updateInfo = trigger.getUpdateInfo();
return NewPlatformVersionNotificationInfo.builder()
.message(JacksonUtil.convertValue(trigger.getMessage(), new TypeReference<>() {}))
.latestVersion(updateInfo.getLatestVersion())
.latestVersionReleaseNotesUrl(updateInfo.getLatestVersionReleaseNotesUrl())
.upgradeInstructionsUrl(updateInfo.getUpgradeInstructionsUrl())
.currentVersion(updateInfo.getCurrentVersion())
.currentVersionReleaseNotesUrl(updateInfo.getCurrentVersionReleaseNotesUrl())
.build();
}

3
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -414,7 +414,8 @@ public class DefaultTbClusterService implements TbClusterService {
|| entityType.equals(EntityType.API_USAGE_STATE)
|| (entityType.equals(EntityType.DEVICE) && msg.getEvent() == ComponentLifecycleEvent.UPDATED)
|| entityType.equals(EntityType.ENTITY_VIEW)
|| entityType.equals(EntityType.EDGE)) {
|| entityType.equals(EntityType.EDGE)
|| entityType.equals(EntityType.NOTIFICATION_RULE)) {
TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer();
Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE);
for (String serviceId : tbCoreServices) {

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

@ -35,12 +35,12 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
@ -274,9 +274,6 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg();
log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg());
forwardToNotificationSchedulerService(notificationSchedulerServiceMsg, callback);
} else if (toCoreMsg.hasNotificationRuleProcessorMsg()) {
Optional<NotificationRuleTrigger> notificationRuleTrigger = encodingService.decode(toCoreMsg.getNotificationRuleProcessorMsg().getTrigger().toByteArray());
notificationRuleTrigger.ifPresent(notificationRuleProcessor::process);
}
} catch (Throwable e) {
log.warn("[{}] Failed to process message: {}", id, msg, e);
@ -361,6 +358,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
callback.onSuccess();
} else if (toCoreNotification.hasToSubscriptionMgrMsg()) {
forwardToSubMgrService(toCoreNotification.getToSubscriptionMgrMsg(), callback);
} else if (toCoreNotification.hasNotificationRuleProcessorMsg()) {
Optional<NotificationRuleTrigger> notificationRuleTrigger = encodingService.decode(toCoreNotification
.getNotificationRuleProcessorMsg().getTrigger().toByteArray());
notificationRuleTrigger.ifPresent(notificationRuleProcessor::process);
callback.onSuccess();
}
if (statsEnabled) {
stats.log(toCoreNotification);

8
application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.service.update;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
@ -28,6 +27,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.PostConstruct;
@ -74,8 +74,8 @@ public class DefaultUpdateService implements UpdateService {
private String version;
private UUID instanceId = null;
@PostConstruct
private void init() {
@AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
public void init() {
version = buildProperties != null ? buildProperties.getVersion() : "unknown";
updateMessage = new UpdateMessage(false, version, "", "", "", "");
if (updatesEnabled) {
@ -132,7 +132,7 @@ public class DefaultUpdateService implements UpdateService {
updateMessage = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v2/thingsboard/updates", request, UpdateMessage.class);
if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) {
notificationRuleProcessor.process(NewPlatformVersionTrigger.builder()
.message(updateMessage)
.updateInfo(updateMessage)
.build());
}
} catch (Exception e) {

7
application/src/main/resources/thingsboard.yml

@ -437,9 +437,6 @@ cache:
assetProfiles:
timeToLiveInMinutes: "${CACHE_SPECS_ASSET_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_ASSET_PROFILES_MAX_SIZE:10000}"
notificationRules:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:1440}"
maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:10000}"
notificationSettings:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_SETTINGS_TTL:10}"
maxSize: "${CACHE_SPECS_NOTIFICATION_SETTINGS_MAX_SIZE:1000}"
@ -480,6 +477,10 @@ cache:
entityCount:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_COUNT_TTL:1440}"
maxSize: "${CACHE_SPECS_ENTITY_COUNT_MAX_SIZE:100000}"
# deliberately placed outside 'specs' group above
notificationRules:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:30}"
maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:1000}"
#Disable this because it is not required.
spring.data.redis.repositories.enabled: false

2
common/cluster-api/src/main/proto/queue.proto

@ -968,7 +968,6 @@ message ToCoreMsg {
EdgeNotificationMsgProto edgeNotificationMsg = 5;
DeviceActivityProto deviceActivityMsg = 6;
NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7;
NotificationRuleProcessorMsg notificationRuleProcessorMsg = 8;
}
/* High priority messages with low latency are handled by ThingsBoard Core Service separately */
@ -983,6 +982,7 @@ message ToCoreNotificationMsg {
bytes toEdgeSyncRequestMsg = 8;
bytes fromEdgeSyncResponseMsg = 9;
SubscriptionMgrMsgProto toSubscriptionMgrMsg = 10;
NotificationRuleProcessorMsg notificationRuleProcessorMsg = 11;
}
/* Messages that are handled by ThingsBoard RuleEngine Service */

1
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -29,7 +29,6 @@ public class CacheConstants {
public static final String TENANTS_CACHE = "tenants";
public static final String TENANTS_EXIST_CACHE = "tenantsExist";
public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
public static final String NOTIFICATION_RULES_CACHE = "notificationRules";
public static final String NOTIFICATION_SETTINGS_CACHE = "notificationSettings";
public static final String ASSET_PROFILE_CACHE = "assetProfiles";

16
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java

@ -19,7 +19,6 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.UpdateMessage;
import java.util.Map;
@ -31,11 +30,22 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Builder
public class NewPlatformVersionNotificationInfo implements RuleOriginatedNotificationInfo {
private Map<String, String> message;
private String latestVersion;
private String latestVersionReleaseNotesUrl;
private String upgradeInstructionsUrl;
private String currentVersion;
private String currentVersionReleaseNotesUrl;
@Override
public Map<String, String> getTemplateData() {
return message;
return mapOf(
"latestVersion", latestVersion,
"latestVersionReleaseNotesUrl", latestVersionReleaseNotesUrl,
"upgradeInstructionsUrl", upgradeInstructionsUrl,
"currentVersion", currentVersion,
"currentVersionReleaseNotesUrl", currentVersionReleaseNotesUrl
);
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionNotificationRuleTriggerConfig.java

@ -20,8 +20,6 @@ import lombok.Data;
@Data
public class NewPlatformVersionNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
// TODO: don't forget to create default notification configs
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.NEW_PLATFORM_VERSION;

2
common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/NewPlatformVersionTrigger.java

@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification
@Builder
public class NewPlatformVersionTrigger implements NotificationRuleTrigger {
private final UpdateMessage message;
private final UpdateMessage updateInfo;
@Override
public NotificationRuleTriggerType getType() {

27
common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java

@ -17,6 +17,7 @@ package org.thingsboard.server.queue.notification;
import com.google.protobuf.ByteString;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
@ -25,6 +26,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.NotificationsTopicService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
@ -32,24 +34,33 @@ import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import java.util.UUID;
@Service
@ConditionalOnMissingBean(NotificationRuleProcessor.class)
@ConditionalOnMissingBean(value = NotificationRuleProcessor.class, ignored = RemoteNotificationRuleProcessor.class)
@RequiredArgsConstructor
@Slf4j
public class RemoteNotificationRuleProcessor implements NotificationRuleProcessor {
private final TbQueueProducerProvider producerProvider;
private final NotificationsTopicService notificationsTopicService;
private final PartitionService partitionService;
private final DataDecodingEncodingService encodingService;
@Override
public void process(NotificationRuleTrigger trigger) {
TransportProtos.NotificationRuleProcessorMsg.Builder msg = TransportProtos.NotificationRuleProcessorMsg.newBuilder()
.setTrigger(ByteString.copyFrom(encodingService.encode(trigger)));
try {
log.trace("Submitting notification rule trigger: {}", trigger);
TransportProtos.NotificationRuleProcessorMsg.Builder msg = TransportProtos.NotificationRuleProcessorMsg.newBuilder()
.setTrigger(ByteString.copyFrom(encodingService.encode(trigger)));
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, trigger.getTenantId(), trigger.getOriginatorEntityId());
producerProvider.getTbCoreMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(),
TransportProtos.ToCoreMsg.newBuilder()
.setNotificationRuleProcessorMsg(msg)
.build()), null);
partitionService.getAllServiceIds(ServiceType.TB_CORE).stream().findAny().ifPresent(serviceId -> {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId);
producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(),
TransportProtos.ToCoreNotificationMsg.newBuilder()
.setNotificationRuleProcessorMsg(msg)
.build()), null);
});
} catch (Exception e) {
log.error("Failed to submit notification rule trigger: {}", trigger, e);
}
}
}

46
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java

@ -17,7 +17,6 @@ package org.thingsboard.server.dao.notification;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
@ -28,43 +27,35 @@ import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheKey;
import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheValue;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class DefaultNotificationRuleService extends AbstractCachedEntityService<NotificationRuleCacheKey, NotificationRuleCacheValue, NotificationRule> implements NotificationRuleService, EntityDaoService {
public class DefaultNotificationRuleService extends AbstractEntityService implements NotificationRuleService, EntityDaoService {
private final NotificationRuleDao notificationRuleDao;
@Override
public NotificationRule saveNotificationRule(TenantId tenantId, NotificationRule notificationRule) {
boolean created = notificationRule.getId() == null;
if (!created) {
if (notificationRule.getId() != null) {
NotificationRule oldNotificationRule = findNotificationRuleById(tenantId, notificationRule.getId());
if (notificationRule.getTriggerType() != oldNotificationRule.getTriggerType()) {
throw new IllegalArgumentException("Notification rule trigger type cannot be updated");
}
}
try {
notificationRule = notificationRuleDao.saveAndFlush(tenantId, notificationRule);
publishEvictEvent(notificationRule);
return notificationRuleDao.saveAndFlush(tenantId, notificationRule);
} catch (Exception e) {
handleEvictEvent(notificationRule);
checkConstraintViolation(e, Map.of(
"uq_notification_rule_name", "Notification rule with such name already exists"
));
throw e;
}
return notificationRule;
}
@Override
@ -89,44 +80,17 @@ public class DefaultNotificationRuleService extends AbstractCachedEntityService<
@Override
public List<NotificationRule> findNotificationRulesByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType) {
NotificationRuleCacheKey cacheKey = NotificationRuleCacheKey.builder()
.tenantId(tenantId)
.triggerType(triggerType)
.build();
return cache.getAndPutInTransaction(cacheKey, () -> NotificationRuleCacheValue.builder()
.notificationRules(notificationRuleDao.findByTenantIdAndTriggerType(tenantId, triggerType))
.build(), false)
.getNotificationRules();
return notificationRuleDao.findByTenantIdAndTriggerType(tenantId, triggerType);
}
@Transactional
@Override
public void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id) {
NotificationRule notificationRule = findNotificationRuleById(tenantId, id);
publishEvictEvent(notificationRule);
notificationRuleDao.removeById(tenantId, id.getId());
}
@Override
public void deleteNotificationRulesByTenantId(TenantId tenantId) {
notificationRuleDao.removeByTenantId(tenantId);
List<NotificationRuleCacheKey> cacheKeys = Arrays.stream(NotificationRuleTriggerType.values())
.map(triggerType -> NotificationRuleCacheKey.builder()
.tenantId(tenantId)
.triggerType(triggerType)
.build())
.collect(Collectors.toList());
cache.evict(cacheKeys);
}
@Override
public void handleEvictEvent(NotificationRule notificationRule) {
NotificationRuleCacheKey cacheKey = NotificationRuleCacheKey.builder()
.tenantId(notificationRule.getTenantId())
.triggerType(notificationRule.getTriggerType())
.build();
cache.evict(cacheKey);
}
@Override

9
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java

@ -46,6 +46,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivi
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig.DeviceEvent;
import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
@ -144,6 +145,14 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
apiUsageLimitRuleTriggerConfig.setNotifyOn(Set.of(ApiUsageStateValue.WARNING, ApiUsageStateValue.DISABLED));
createRule(tenantId, "API usage limit", apiUsageLimitNotificationTemplate.getId(), apiUsageLimitRuleTriggerConfig,
List.of(affectedTenantAdmins.getId(), sysAdmins.getId()), "Send notification to tenant admins and system admins when API feature usage state changed");
NotificationTemplate newPlatformVersionNotificationTemplate = createTemplate(tenantId, "New platform version notification", NotificationType.NEW_PLATFORM_VERSION,
"New version <b>${latestVersion}</b> is available",
"Current platform version is ${currentVersion}",
null, "Open release notes", "${latestVersionReleaseNotesUrl}");
NewPlatformVersionNotificationRuleTriggerConfig newPlatformVersionRuleTriggerConfig = new NewPlatformVersionNotificationRuleTriggerConfig();
createRule(tenantId, "New platform version", newPlatformVersionNotificationTemplate.getId(), newPlatformVersionRuleTriggerConfig,
List.of(sysAdmins.getId(), tenantAdmins.getId()), "Send notification to system admins and tenant admins when new platform version is available");
return;
}

6
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java

@ -48,6 +48,12 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im
@Override
public NotificationTemplate saveNotificationTemplate(TenantId tenantId, NotificationTemplate notificationTemplate) {
if (notificationTemplate.getId() != null) {
NotificationTemplate oldNotificationTemplate = findNotificationTemplateById(tenantId, notificationTemplate.getId());
if (notificationTemplate.getNotificationType() != oldNotificationTemplate.getNotificationType()) {
throw new IllegalArgumentException("Notification type cannot be updated");
}
}
try {
return notificationTemplateDao.saveAndFlush(tenantId, notificationTemplate);
} catch (Exception e) {

43
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheKey.java

@ -1,43 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class NotificationRuleCacheKey implements Serializable {
private static final long serialVersionUID = 5987113265482170L;
private TenantId tenantId;
private NotificationRuleTriggerType triggerType;
@Override
public String toString() {
return tenantId + "_" + triggerType;
}
}

32
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCaffeineCache.java

@ -1,32 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service
public class NotificationRuleCaffeineCache extends CaffeineTbTransactionalCache<NotificationRuleCacheKey, NotificationRuleCacheValue> {
public NotificationRuleCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.NOTIFICATION_RULES_CACHE);
}
}

35
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleRedisCache.java

@ -1,35 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbFSTRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service
public class NotificationRuleRedisCache extends RedisTbTransactionalCache<NotificationRuleCacheKey, NotificationRuleCacheValue> {
public NotificationRuleRedisCache(CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory, TBRedisCacheConfiguration configuration) {
super(CacheConstants.NOTIFICATION_RULES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
}
}

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

@ -804,7 +804,7 @@ CREATE TABLE IF NOT EXISTS notification_template (
tenant_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
notification_type VARCHAR(50) NOT NULL,
configuration VARCHAR(10000) NOT NULL,
configuration VARCHAR(10000000) NOT NULL,
CONSTRAINT uq_notification_template_name UNIQUE (tenant_id, name)
);
@ -827,7 +827,7 @@ CREATE TABLE IF NOT EXISTS notification_request (
tenant_id UUID NOT NULL,
targets VARCHAR(10000) NOT NULL,
template_id UUID,
template VARCHAR(10000),
template VARCHAR(10000000),
info VARCHAR(1000),
additional_config VARCHAR(1000),
originator_entity_id UUID,

14
ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html

@ -478,6 +478,20 @@
</section>
</form>
</mat-step>
<mat-step *ngIf="ruleNotificationForm.get('triggerType').value === triggerType.NEW_PLATFORM_VERSION"
[stepControl]="newPlatformVersionTemplateForm">
<ng-template matStepLabel>{{ 'notification.new-platform-version-trigger-settings' | translate }}</ng-template>
<form [formGroup]="ruleNotificationForm">
<section formGroupName="additionalConfig">
<mat-form-field class="mat-block">
<mat-label translate>notification.description</mat-label>
<input matInput formControlName="description">
</mat-form-field>
</section>
</form>
</mat-step>
</mat-horizontal-stepper>
</div>
<mat-divider></mat-divider>

20
ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts

@ -94,6 +94,7 @@ export class RuleNotificationDialogComponent extends
ruleEngineEventsTemplateForm: FormGroup;
entitiesLimitTemplateForm: FormGroup;
apiUsageLimitTemplateForm: FormGroup;
newPlatformVersionTemplateForm: FormGroup;
triggerType = TriggerType;
triggerTypes: TriggerType[];
@ -294,6 +295,12 @@ export class RuleNotificationDialogComponent extends
})
});
this.newPlatformVersionTemplateForm = this.fb.group({
triggerConfig: this.fb.group({
})
});
this.triggerTypeFormsMap = new Map<TriggerType, FormGroup>([
[TriggerType.ALARM, this.alarmTemplateForm],
[TriggerType.ALARM_COMMENT, this.alarmCommentTemplateForm],
@ -302,7 +309,8 @@ export class RuleNotificationDialogComponent extends
[TriggerType.ALARM_ASSIGNMENT, this.alarmAssignmentTemplateForm],
[TriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, this.ruleEngineEventsTemplateForm],
[TriggerType.ENTITIES_LIMIT, this.entitiesLimitTemplateForm],
[TriggerType.API_USAGE_LIMIT, this.apiUsageLimitTemplateForm]
[TriggerType.API_USAGE_LIMIT, this.apiUsageLimitTemplateForm],
[TriggerType.NEW_PLATFORM_VERSION, this.newPlatformVersionTemplateForm]
]);
if (data.isAdd || data.isCopy) {
@ -434,10 +442,16 @@ export class RuleNotificationDialogComponent extends
}
private allowTriggerTypes(): TriggerType[] {
const sysAdminAllowTriggerTypes = new Set([
TriggerType.ENTITIES_LIMIT,
TriggerType.API_USAGE_LIMIT,
TriggerType.NEW_PLATFORM_VERSION,
]);
if (this.isSysAdmin()) {
return [TriggerType.ENTITIES_LIMIT, TriggerType.API_USAGE_LIMIT];
return Array.from(sysAdminAllowTriggerTypes);
}
return Object.values(TriggerType).filter(type => type !== TriggerType.ENTITIES_LIMIT && type !== TriggerType.API_USAGE_LIMIT);
return Object.values(TriggerType).filter(type => !sysAdminAllowTriggerTypes.has(type));
}
get allowEntityTypeForEntityAction(): EntityType[] {

11
ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts

@ -177,10 +177,15 @@ export class TemplateNotificationDialogComponent
}
private allowNotificationType(): NotificationType[] {
const sysAdminAllowNotificationTypes = new Set([
NotificationType.ENTITIES_LIMIT,
NotificationType.API_USAGE_LIMIT,
NotificationType.NEW_PLATFORM_VERSION,
]);
if (this.isSysAdmin()) {
return [NotificationType.GENERAL, NotificationType.ENTITIES_LIMIT, NotificationType.API_USAGE_LIMIT];
return [NotificationType.GENERAL, ...sysAdminAllowNotificationTypes];
}
return Object.values(NotificationType)
.filter(type => type !== NotificationType.ENTITIES_LIMIT && type !== NotificationType.API_USAGE_LIMIT);
return Object.values(NotificationType).filter(type => !sysAdminAllowNotificationTypes.has(type));
}
}

11
ui-ngx/src/app/shared/models/notification.models.ts

@ -442,6 +442,7 @@ export enum NotificationType {
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT = 'RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT',
ENTITIES_LIMIT = 'ENTITIES_LIMIT',
API_USAGE_LIMIT = 'API_USAGE_LIMIT',
NEW_PLATFORM_VERSION = 'NEW_PLATFORM_VERSION',
RULE_ENGINE = 'RULE_ENGINE'
}
@ -536,6 +537,12 @@ export const NotificationTemplateTypeTranslateMap = new Map<NotificationType, No
helpId: 'notification/api_usage_limit'
}
],
[NotificationType.NEW_PLATFORM_VERSION,
{
name: 'notification.template-type.new-platform-version',
helpId: 'notification/new_platform_version'
}
],
[NotificationType.RULE_ENGINE,
{
name: 'notification.template-type.rule-engine',
@ -552,7 +559,8 @@ export enum TriggerType {
ALARM_ASSIGNMENT = 'ALARM_ASSIGNMENT',
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT = 'RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT',
ENTITIES_LIMIT = 'ENTITIES_LIMIT',
API_USAGE_LIMIT = 'API_USAGE_LIMIT'
API_USAGE_LIMIT = 'API_USAGE_LIMIT',
NEW_PLATFORM_VERSION = 'NEW_PLATFORM_VERSION'
}
export const TriggerTypeTranslationMap = new Map<TriggerType, string>([
@ -564,4 +572,5 @@ export const TriggerTypeTranslationMap = new Map<TriggerType, string>([
[TriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, 'notification.trigger.rule-engine-lifecycle-event'],
[TriggerType.ENTITIES_LIMIT, 'notification.trigger.entities-limit'],
[TriggerType.API_USAGE_LIMIT, 'notification.trigger.api-usage-limit'],
[TriggerType.NEW_PLATFORM_VERSION, 'notification.trigger.new-platform-version'],
]);

46
ui-ngx/src/assets/help/en_US/notification/new_platform_version.md

@ -0,0 +1,46 @@
#### New platform version notification templatization
<div class="divider"></div>
<br/>
Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type.
See the available types and parameters below:
Available template parameters:
* *recipientEmail* - email of the recipient;
* *recipientFirstName* - first name of the recipient;
* *recipientLastName* - last name of the recipient;
* *latestVersion* - the latest platform version available;
* *latestVersionReleaseNotesUrl* - release notes link for latest version;
* *upgradeInstructionsUrl* - upgrade instructions link for latest version;
* *currentVersion* - the current platform version
* *currentVersionReleaseNotesUrl* - release notes link for current version;
Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`.
You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div>
##### Examples
Let's assume that new 3.5.0 version is released but currently deployed version is 3.4.4. The following template:
```text
New version ${latestVersion} is available. Current version is ${currentVersion}
{:copy-code}
```
will be transformed to:
```text
New version 3.5.0 is available. Current version is 3.4.4
{:copy-code}
```
<br>
<br>

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

@ -2761,6 +2761,7 @@
"all": "All",
"api-feature-hint": "If the field is empty, the trigger will be applied to all api features",
"api-usage-trigger-settings": "API usage trigger settings",
"new-platform-version-trigger-settings": "New platform version trigger settings",
"at-least-one-should-be-selected": "At least one should be selected",
"basic-settings": "Basic settings",
"button-text": "Button text",
@ -2944,7 +2945,8 @@
"entity-action": "Entity action",
"general": "General",
"rule-engine-lifecycle-event": "Rule engine lifecycle event",
"rule-engine": "Rule engine"
"rule-engine": "Rule engine",
"new-platform-version": "New platform version"
},
"templates": "Templates",
"tenant-profiles-list-rule-hint": "If the field is empty, the trigger will be applied to all tenant profiles",
@ -2961,6 +2963,7 @@
"entities-limit": "Entities limit",
"entity-action": "Entity action",
"rule-engine-lifecycle-event": "Rule engine lifecycle event",
"new-platform-version": "New platform version",
"trigger": "Trigger",
"trigger-required": "Trigger is required"
},

Loading…
Cancel
Save