Browse Source

Merge remote-tracking branch 'upstream/feature/notification-system' into AD/notification/rule

pull/8022/head
Artem Dzhereleiko 4 years ago
parent
commit
72ab2ad649
  1. 1
      application/src/main/data/upgrade/3.4.3/schema_update.sql
  2. 9
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  3. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java
  4. 4
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerProcessor.java
  5. 12
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  6. 80
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  7. 15
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java
  8. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java
  9. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java
  10. 25
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java
  11. 7
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerConfig.java
  12. 17
      common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java
  13. 13
      common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/SlackNotificationDeliveryMethodConfig.java
  14. 6
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java
  15. 10
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackNotificationTargetConfig.java
  16. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  17. 8
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java
  18. 1
      dao/src/main/resources/sql/schema-entities.sql
  19. 2
      ui-ngx/src/app/core/http/entity.service.ts
  20. 5
      ui-ngx/src/app/core/http/notification.service.ts
  21. 7
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/targets-table-config.ts
  22. 64
      ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.component.html
  23. 39
      ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.scss
  24. 42
      ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.ts
  25. 27
      ui-ngx/src/app/shared/models/notification.models.ts
  26. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

1
application/src/main/data/upgrade/3.4.3/schema_update.sql

@ -45,6 +45,7 @@ CREATE TABLE IF NOT EXISTS notification_rule (
trigger_type VARCHAR(50) NOT NULL,
trigger_config VARCHAR(1000) NOT NULL,
recipients_config VARCHAR(10000) NOT NULL,
additional_config VARCHAR(255),
CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name)
);
CREATE INDEX IF NOT EXISTS idx_notification_rule_tenant_id_created_time ON notification_rule(tenant_id, created_time DESC);

9
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -113,17 +113,13 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (!template.isEnabled()) return;
if (settings.getDeliveryMethodsConfigs().containsKey(deliveryMethod) &&
!settings.getDeliveryMethodsConfigs().get(deliveryMethod).isEnabled()) {
throw new IllegalArgumentException("Delivery method " + deliveryMethod + " is disabled");
}
if (deliveryMethod == NotificationDeliveryMethod.SLACK) {
if (!settings.getDeliveryMethodsConfigs().containsKey(deliveryMethod)) {
throw new IllegalArgumentException("Slack must be configured in the settings");
}
}
if (targets.stream().noneMatch(target -> target.getType().getSupportedDeliveryMethods().contains(deliveryMethod))) {
throw new IllegalArgumentException("Target for " + deliveryMethod + " delivery method is missing");
throw new IllegalArgumentException("Target for " + deliveryMethod.getName() + " delivery method is missing");
}
});
@ -195,8 +191,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
case SLACK: {
SlackNotificationTargetConfig slackTargetConfig = (SlackNotificationTargetConfig) target.getConfiguration();
SlackConversation conversation = new SlackConversation(slackTargetConfig.getConversationId(), slackTargetConfig.getConversationName());
recipients = List.of(conversation);
recipients = List.of(slackTargetConfig.getConversation());
break;
}
default: {

2
application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java

@ -151,7 +151,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
notificationExecutor.submit(() -> {
try {
log.debug("Submitting notification request for rule '{}' with delay of {} ms to targets {}", rule.getName(), delay, targets);
log.debug("Submitting notification request for rule '{}' with delay of {} sec to targets {}", rule.getName(), delay, targets);
submitNotificationRequest(targets, rule, originatorEntityId, notificationInfo, delay);
} catch (Exception e) {
log.error("Failed to submit notification request for rule {}", rule.getId(), e);

4
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerProcessor.java

@ -38,10 +38,10 @@ public class DeviceInactivityNotificationRuleTriggerProcessor implements Notific
public boolean matchesFilter(TbMsg ruleEngineMsg, DeviceInactivityNotificationRuleTriggerConfig triggerConfig) {
DeviceId deviceId = (DeviceId) ruleEngineMsg.getOriginator();
if (CollectionUtils.isNotEmpty(triggerConfig.getDevices())) {
return triggerConfig.getDevices().contains(deviceId);
return triggerConfig.getDevices().contains(deviceId.getId());
} else if (CollectionUtils.isNotEmpty(triggerConfig.getDeviceProfiles())) {
DeviceProfile deviceProfile = deviceProfileCache.get(TenantId.SYS_TENANT_ID, deviceId);
return deviceProfile != null && triggerConfig.getDeviceProfiles().contains(deviceProfile.getId());
return deviceProfile != null && triggerConfig.getDeviceProfiles().contains(deviceProfile.getUuidId());
} else {
return true;
}

12
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.notification.targets.platform.AllUsers
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
@ -58,11 +59,9 @@ import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.notification.NotificationDao;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate;
import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -107,8 +106,8 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
wsClient.subscribeForUnreadNotificationsCount();
wsClient.waitForReply(true);
UnreadNotificationsCountUpdate update = wsClient.getLastCountUpdate();
assertThat(update.getTotalUnreadCount()).isEqualTo(2);
await().atMost(2, TimeUnit.SECONDS)
.until(() -> wsClient.getLastCountUpdate().getTotalUnreadCount() == 2);
}
@Test
@ -567,8 +566,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
public void testSlackNotifications() throws Exception {
NotificationSettings settings = new NotificationSettings();
SlackNotificationDeliveryMethodConfig slackConfig = new SlackNotificationDeliveryMethodConfig();
slackConfig.setMethod(NotificationDeliveryMethod.SLACK);
slackConfig.setEnabled(true);
String slackToken = "xoxb-123123123";
slackConfig.setBotToken(slackToken);
settings.setDeliveryMethodsConfigs(Map.of(
@ -596,8 +593,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
notificationTarget.setName(conversationName + " in Slack");
notificationTarget.setType(NotificationTargetType.SLACK);
SlackNotificationTargetConfig targetConfig = new SlackNotificationTargetConfig();
targetConfig.setConversationId(conversationId);
targetConfig.setConversationName(conversationName);
targetConfig.setConversation(new SlackConversation(conversationId, conversationName));
notificationTarget.setConfiguration(targetConfig);
notificationTarget = saveNotificationTarget(notificationTarget);

80
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.data.util.Pair;
import org.thingsboard.common.util.JacksonUtil;
@ -41,6 +42,7 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe
import org.thingsboard.server.common.data.id.NotificationRuleId;
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.NotificationRequestInfo;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo;
@ -60,6 +62,7 @@ import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
@ -67,16 +70,15 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.offset;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
@ -84,6 +86,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@SpyBean
private AlarmSubscriptionService alarmSubscriptionService;
@Autowired
private NotificationRequestService notificationRequestService;
@SpyBean
private AlarmService alarmService;
@ -234,15 +238,13 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
wsClient.close();
});
// TODO: test clear rule + alarm not escalated
// TODO: test severity changes
}
@Test
public void testNotificationRuleProcessing_alarmTrigger_clearRule() throws Exception {
/*
String notificationSubject = "New alarm '${alarmType}'";
String notificationText = "Status: ${alarmStatus}, severity: ${alarmSeverity}";
String notificationSubject = "${alarmSeverity} alarm '${alarmType}' is ${alarmStatus}";
String notificationText = "${alarmId}";
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, notificationSubject, notificationText, NotificationDeliveryMethod.PUSH);
NotificationRule notificationRule = new NotificationRule();
@ -250,42 +252,59 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
notificationRule.setTemplateId(notificationTemplate.getId());
notificationRule.setTriggerType(NotificationRuleTriggerType.ALARM);
String alarmType = "myBoolIsTrue";
DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(notificationRule.getId(), alarmType);
Device device = createDevice("Device 1", deviceProfile.getName(), "1234");
AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig();
triggerConfig.setAlarmTypes(null);
triggerConfig.setAlarmTypes(Set.of(alarmType));
triggerConfig.setAlarmSeverities(null);
AlarmNotificationRuleTriggerConfig.ClearRule clearRule = new AlarmNotificationRuleTriggerConfig.ClearRule();
clearRule.setAlarmStatus(AlarmStatus.CLEARED_UNACK);
triggerConfig.setClearRule(clearRule);
notificationRule.setTriggerConfig(triggerConfig);
EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig();
recipientsConfig.setTriggerType(NotificationRuleTriggerType.ALARM);
Map<Integer, List<UUID>> escalationTable = new HashMap<>();
recipientsConfig.setEscalationTable(escalationTable);
Map<Integer, NotificationApiWsClient> clients = new HashMap<>();
for (int delay = 0; delay <= 5; delay++) {
Pair<User, NotificationApiWsClient> userAndClient = createUserAndConnectWsClient(Authority.TENANT_ADMIN);
NotificationTarget notificationTarget = createNotificationTarget(userAndClient.getFirst().getId());
escalationTable.put(delay, List.of(notificationTarget.getUuidId()));
clients.put(delay, userAndClient.getSecond());
}
notificationRule.setRecipientsConfig(recipientsConfig);
notificationRule = saveNotificationRule(notificationRule);
escalationTable.put(0, List.of(createNotificationTarget(tenantAdminUserId).getUuidId()));
escalationTable.put(1000, List.of(createNotificationTarget(customerUserId).getUuidId()));
String alarmType = "myBoolIsTrue";
DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(notificationRule.getId(), alarmType);
Device device = createDevice("Device 1", deviceProfile.getName(), "1234");
clients.values().forEach(wsClient -> {
wsClient.subscribeForUnreadNotifications(10).waitForReply(true);
wsClient.registerWaitForUpdate();
});
notificationRule.setRecipientsConfig(recipientsConfig);
notificationRule = saveNotificationRule(notificationRule);
getWsClient().subscribeForUnreadNotifications(10).waitForReply(true);
getWsClient().registerWaitForUpdate();
JsonNode attr = JacksonUtil.newObjectNode()
.set("bool", BooleanNode.TRUE);
doPost("/api/plugins/telemetry/" + device.getId() + "/" + DataConstants.SHARED_SCOPE, attr);
verify(alarmSubscriptionService, timeout(2000)).createOrUpdateAlarm(argThat(alarm -> alarm.getType().equals(alarmType)));
Alarm alarm = alarmSubscri
*/
await().atMost(2, TimeUnit.SECONDS)
.until(() -> alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType).get() != null);
Alarm alarm = alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType).get();
getWsClient().waitForUpdate(true);
Notification notification = getWsClient().getLastDataUpdate().getUpdate();
assertThat(notification.getSubject()).isEqualTo("CRITICAL alarm '" + alarmType + "' is ACTIVE_UNACK");
assertThat(notification.getInfo()).asInstanceOf(type(AlarmNotificationInfo.class))
.extracting(AlarmNotificationInfo::getAlarmId).isEqualTo(alarm.getUuidId());
await().atMost(2, TimeUnit.SECONDS).until(() -> findNotificationRequests(EntityType.ALARM).getTotalElements() == escalationTable.size());
NotificationRequestInfo scheduledNotificationRequest = findNotificationRequests(EntityType.ALARM).getData().stream()
.filter(NotificationRequest::isScheduled)
.findFirst().orElse(null);
assertThat(scheduledNotificationRequest).extracting(NotificationRequest::getInfo).isEqualTo(notification.getInfo());
getWsClient().registerWaitForUpdate();
alarmSubscriptionService.clearAlarm(tenantId, alarm.getId(), null, System.currentTimeMillis());
getWsClient().waitForUpdate(true);
notification = getWsClient().getLastDataUpdate().getNotifications().iterator().next();
assertThat(notification.getSubject()).isEqualTo("CRITICAL alarm '" + alarmType + "' is CLEARED_UNACK");
assertThat(findNotificationRequests(EntityType.ALARM).getData()).filteredOn(NotificationRequest::isScheduled).isEmpty();
}
@Test
@ -357,4 +376,9 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
PageLink pageLink = new PageLink(10);
return doGetTypedWithPageLink("/api/notification/rules?", new TypeReference<PageData<NotificationRuleInfo>>() {}, pageLink);
}
private PageData<NotificationRequestInfo> findNotificationRequests(EntityType originatorType) {
return notificationRequestService.findNotificationRequestsInfosByTenantIdAndOriginatorType(tenantId, originatorType, new PageLink(100));
}
}

15
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java

@ -15,11 +15,18 @@
*/
package org.thingsboard.server.common.data.notification;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum NotificationDeliveryMethod {
PUSH,
EMAIL,
SMS,
SLACK
PUSH("push-notification"),
EMAIL("email"),
SMS("SMS"),
SLACK("Slack");
@Getter
private final String name;
}

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java

@ -92,4 +92,9 @@ public class NotificationRequest extends BaseData<NotificationRequestId> impleme
return status == NotificationRequestStatus.SENT;
}
@JsonIgnore
public boolean isScheduled() {
return status == NotificationRequestStatus.SCHEDULED;
}
}

3
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java

@ -52,6 +52,8 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
@Valid
private NotificationRuleRecipientsConfig recipientsConfig; // todo: add pg_tgrm index (but index is 2.5x size of the column)
private NotificationRuleConfig additionalConfig;
public NotificationRule(NotificationRule other) {
super(other);
this.tenantId = other.tenantId;
@ -60,6 +62,7 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
this.triggerType = other.triggerType;
this.triggerConfig = other.triggerConfig;
this.recipientsConfig = other.recipientsConfig;
this.additionalConfig = other.additionalConfig;
}
@JsonIgnore

25
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.rule;
import lombok.Data;
@Data
public class NotificationRuleConfig {
private String description;
}

7
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerConfig.java

@ -16,16 +16,15 @@
package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Data;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import java.util.Set;
import java.util.UUID;
@Data
public class DeviceInactivityNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
private Set<DeviceId> devices;
private Set<DeviceProfileId> deviceProfiles;
private Set<UUID> devices;
private Set<UUID> deviceProfiles;
// set either devices or profiles
@Override

17
common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java

@ -15,26 +15,21 @@
*/
package org.thingsboard.server.common.data.notification.settings;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.constraints.NotNull;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method", include = JsonTypeInfo.As.EXISTING_PROPERTY,
visible = true, defaultImpl = NotificationDeliveryMethodConfig.class)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method")
@JsonSubTypes({
@Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class),
@Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class)
})
@Data
public class NotificationDeliveryMethodConfig {
public interface NotificationDeliveryMethodConfig {
private boolean enabled;
@NotNull
private NotificationDeliveryMethod method;
@JsonIgnore
NotificationDeliveryMethod getMethod();
}

13
common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/SlackNotificationDeliveryMethodConfig.java

@ -16,12 +16,19 @@
package org.thingsboard.server.common.data.notification.settings;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.constraints.NotEmpty;
@Data
@EqualsAndHashCode(callSuper = true)
public class SlackNotificationDeliveryMethodConfig extends NotificationDeliveryMethodConfig {
public class SlackNotificationDeliveryMethodConfig implements NotificationDeliveryMethodConfig {
@NotEmpty
private String botToken;
@Override
public NotificationDeliveryMethod getMethod() {
return NotificationDeliveryMethod.SLACK;
}
}

6
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java

@ -20,10 +20,16 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import javax.validation.constraints.NotEmpty;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SlackConversation implements NotificationRecipient {
@NotEmpty
private String id;
@NotEmpty
private String name;
}

10
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackNotificationTargetConfig.java

@ -20,17 +20,17 @@ import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import javax.validation.constraints.NotEmpty;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Data
@EqualsAndHashCode(callSuper = true)
public class SlackNotificationTargetConfig extends NotificationTargetConfig {
private SlackConversationType conversationType;
@NotEmpty
private String conversationId;
@NotEmpty
private String conversationName;
@NotNull
@Valid
private SlackConversation conversation;
@Override
public NotificationTargetType getType() {

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

@ -678,6 +678,7 @@ public class ModelConstants {
public static final String NOTIFICATION_RULE_TRIGGER_TYPE_PROPERTY = "trigger_type";
public static final String NOTIFICATION_RULE_TRIGGER_CONFIG_PROPERTY = "trigger_config";
public static final String NOTIFICATION_RULE_RECIPIENTS_CONFIG_PROPERTY = "recipients_config";
public static final String NOTIFICATION_RULE_ADDITIONAL_CONFIG_PROPERTY = "additional_config";
public static final String NOTIFICATION_TEMPLATE_TABLE_NAME = "notification_template";
public static final String NOTIFICATION_TEMPLATE_NOTIFICATION_TYPE_PROPERTY = "notification_type";

8
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java

@ -23,6 +23,7 @@ import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
@ -64,6 +65,10 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
@Column(name = ModelConstants.NOTIFICATION_RULE_RECIPIENTS_CONFIG_PROPERTY, nullable = false)
private JsonNode recipientsConfig;
@Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_RULE_ADDITIONAL_CONFIG_PROPERTY)
private JsonNode additionalConfig;
public NotificationRuleEntity() {}
public NotificationRuleEntity(NotificationRule notificationRule) {
@ -75,6 +80,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
setTriggerType(notificationRule.getTriggerType());
setTriggerConfig(toJson(notificationRule.getTriggerConfig()));
setRecipientsConfig(toJson(notificationRule.getRecipientsConfig()));
setAdditionalConfig(toJson(notificationRule.getAdditionalConfig()));
}
public NotificationRuleEntity(NotificationRuleEntity other) {
@ -86,6 +92,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
this.triggerType = other.triggerType;
this.triggerConfig = other.triggerConfig;
this.recipientsConfig = other.recipientsConfig;
this.additionalConfig = other.additionalConfig;
}
@Override
@ -99,6 +106,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
notificationRule.setTriggerType(triggerType);
notificationRule.setTriggerConfig(fromJson(triggerConfig, NotificationRuleTriggerConfig.class));
notificationRule.setRecipientsConfig(fromJson(recipientsConfig, NotificationRuleRecipientsConfig.class));
notificationRule.setAdditionalConfig(fromJson(additionalConfig, NotificationRuleConfig.class));
return notificationRule;
}

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

@ -808,6 +808,7 @@ CREATE TABLE IF NOT EXISTS notification_rule (
trigger_type VARCHAR(50) NOT NULL,
trigger_config VARCHAR(1000) NOT NULL,
recipients_config VARCHAR(10000) NOT NULL,
additional_config VARCHAR(255),
CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name)
);

2
ui-ngx/src/app/core/http/entity.service.ts

@ -252,7 +252,7 @@ export class EntityService {
entityIds);
break;
case EntityType.NOTIFICATION_TARGET:
observable = of([]);
observable = this.notificationService.getNotificationTargetsByIds(entityIds, config);
break;
}
return observable;

5
ui-ngx/src/app/core/http/notification.service.ts

@ -122,6 +122,11 @@ export class NotificationService {
return this.http.delete<void>(`/api/notification/target/${id}`, defaultHttpOptionsFromConfig(config));
}
public getNotificationTargetsByIds(ids: string[], config?: RequestConfig): Observable<Array<NotificationTarget>> {
return this.http.get<Array<NotificationTarget>>(`/api/notification/targets?ids=${ids.join(',')}`,
defaultHttpOptionsFromConfig(config));
}
public getNotificationTargets(pageLink: PageLink, config?: RequestConfig): Observable<PageData<NotificationTarget>> {
return this.http.get<PageData<NotificationTarget>>(`/api/notification/targets${pageLink.toQuery()}`,
defaultHttpOptionsFromConfig(config));

7
ui-ngx/src/app/modules/home/pages/notification-center/notification-table/targets-table-config.ts

@ -21,7 +21,7 @@ import {
} from '@home/models/entity/entities-table-config.models';
import { EntityTypeResource } from '@shared/models/entity-type.models';
import { Direction } from '@shared/models/page/sort-order';
import { NotificationTarget, NotificationTargetConfigTypeTranslateMap } from '@shared/models/notification.models';
import { NotificationTarget, NotificationTargetTypeTranslationMap } from '@shared/models/notification.models';
import { NotificationService } from '@core/http/notification.service';
import { TranslateService } from '@ngx-translate/core';
import {
@ -62,9 +62,8 @@ export class TargetsTableConfig extends EntityTableConfig<NotificationTarget> {
this.columns.push(
new EntityTableColumn<NotificationTarget>('name', 'notification.notification-target', '20%'),
new EntityTableColumn<NotificationTarget>('configuration.type', 'notification.type', '40%',
(target) => this.translate.instant(NotificationTargetConfigTypeTranslateMap.get(target.configuration.type)),
() => ({}), false),
new EntityTableColumn<NotificationTarget>('type', 'notification.type', '40%',
(target) => this.translate.instant(NotificationTargetTypeTranslationMap.get(target.type))),
new EntityTableColumn<NotificationTarget>('configuration.description', 'notification.description', '40%',
(target) => target.configuration.description || '',
() => ({}), false)

64
ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.component.html

@ -36,35 +36,45 @@
{{ 'notification.target-name-required' | translate }}
</mat-error>
</mat-form-field>
<mat-radio-group formControlName="type">
<mat-radio-button *ngFor="let notificationTargetType of notificationTargetTypes" [value]="notificationTargetType">
<div fxLayout="column">
<div>{{ notificationTargetTypeTranslationMap.get(notificationTargetType) | translate }}</div>
</div>
</mat-radio-button>
</mat-radio-group>
<section formGroupName="configuration">
<mat-form-field class="mat-block">
<mat-label translate>notification.target-type.type</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let type of notificationTargetConfigTypes" [value]="type">
{{ notificationTargetConfigTypeTranslateMap.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<tb-entity-list
required
formControlName="usersIds"
[entityType]="entityType.USER"
labelText="user.user-list"
placeholderText="user.user"
requiredText="user.user-list-required"
*ngIf="targetNotificationForm.get('configuration.type').value === notificationTargetConfigType.USER_LIST">
</tb-entity-list>
<section
*ngIf="targetNotificationForm.get('configuration.type').value === notificationTargetConfigType.CUSTOMER_USERS">
<mat-slide-toggle formControlName="getCustomerIdFromOriginatorEntity">
{{ 'notification.get-customer-id-from-originator' | translate }}
</mat-slide-toggle>
<tb-entity-autocomplete
<section formGroupName="usersFilter"
*ngIf="targetNotificationForm.get('type').value === notificationTargetType.PLATFORM_USERS">
<mat-form-field class="mat-block">
<mat-label translate>notification.target-type.type</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let type of notificationTargetConfigTypes" [value]="type">
{{ notificationTargetConfigTypeTranslateMap.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<tb-entity-list
required
formControlName="customerId"
[entityType]="entityType.CUSTOMER"
*ngIf="!targetNotificationForm.get('configuration.getCustomerIdFromOriginatorEntity').value">
</tb-entity-autocomplete>
formControlName="usersIds"
[entityType]="entityType.USER"
labelText="user.user-list"
placeholderText="user.user"
requiredText="user.user-list-required"
*ngIf="targetNotificationForm.get('configuration.usersFilter.type').value === notificationTargetConfigType.USER_LIST">
</tb-entity-list>
<section
*ngIf="targetNotificationForm.get('configuration.usersFilter.type').value === notificationTargetConfigType.CUSTOMER_USERS">
<mat-slide-toggle formControlName="getCustomerIdFromOriginatorEntity">
{{ 'notification.get-customer-id-from-originator' | translate }}
</mat-slide-toggle>
<tb-entity-autocomplete
required
formControlName="customerId"
[entityType]="entityType.CUSTOMER"
*ngIf="!targetNotificationForm.get('configuration.usersFilter.getCustomerIdFromOriginatorEntity').value">
</tb-entity-autocomplete>
</section>
</section>
<mat-form-field class="mat-block">
<mat-label translate>notification.description</mat-label>

39
ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.scss

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../../../../../../theme";
:host ::ng-deep {
.mat-radio-group {
display: flex;
flex-direction: row;
margin-bottom: 8px;
.mat-radio-button {
flex: 1 1 100%;
padding: 14px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 6px;
&:not(:last-child){
margin-right: 8px;
}
&.mat-radio-checked {
border-color: map_get($tb-accent, 500);
}
}
}
}

42
ui-ngx/src/app/modules/home/pages/notification-center/targets-table/target-notification-dialog.componet.ts

@ -17,7 +17,9 @@
import {
NotificationTarget,
NotificationTargetConfigType,
NotificationTargetConfigTypeTranslateMap
NotificationTargetConfigTypeTranslateMap,
NotificationTargetType,
NotificationTargetTypeTranslationMap
} from '@shared/models/notification.models';
import { Component, Inject, OnDestroy } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
@ -40,12 +42,15 @@ export interface TargetsNotificationDialogData {
@Component({
selector: 'tb-target-notification-dialog',
templateUrl: './target-notification-dialog.component.html',
styleUrls: []
styleUrls: ['target-notification-dialog.componet.scss']
})
export class TargetNotificationDialogComponent extends
DialogComponent<TargetNotificationDialogComponent, NotificationTarget> implements OnDestroy {
targetNotificationForm: FormGroup;
notificationTargetType = NotificationTargetType;
notificationTargetTypes: NotificationTargetType[] = Object.values(NotificationTargetType);
notificationTargetTypeTranslationMap = NotificationTargetTypeTranslationMap;
notificationTargetConfigType = NotificationTargetConfigType;
notificationTargetConfigTypes: NotificationTargetConfigType[] = Object.values(NotificationTargetConfigType);
notificationTargetConfigTypeTranslateMap = NotificationTargetConfigTypeTranslateMap;
@ -68,44 +73,46 @@ export class TargetNotificationDialogComponent extends
this.targetNotificationForm = this.fb.group({
name: [null, Validators.required],
type: [NotificationTargetType.PLATFORM_USERS],
configuration: this.fb.group({
type: [NotificationTargetConfigType.ALL_USERS],
description: [null],
usersIds: [{value: null, disabled: true}, Validators.required],
customerId: [{value: null, disabled: true}, Validators.required],
getCustomerIdFromOriginatorEntity: [{value: false, disabled: true}],
usersFilter: this.fb.group({
type: [NotificationTargetConfigType.ALL_USERS],
usersIds: [{value: null, disabled: true}, Validators.required],
customerId: [{value: null, disabled: true}, Validators.required],
getCustomerIdFromOriginatorEntity: [{value: false, disabled: true}]
})
})
});
this.targetNotificationForm.get('configuration.type').valueChanges.pipe(
this.targetNotificationForm.get('configuration.usersFilter.type').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((type: NotificationTargetConfigType) => {
this.targetNotificationForm.get('configuration').disable({emitEvent: false});
this.targetNotificationForm.get('configuration.usersFilter').disable({emitEvent: false});
switch (type) {
case NotificationTargetConfigType.USER_LIST:
this.targetNotificationForm.get('configuration.usersIds').enable({emitEvent: false});
this.targetNotificationForm.get('configuration.usersFilter.usersIds').enable({emitEvent: false});
break;
case NotificationTargetConfigType.CUSTOMER_USERS:
this.targetNotificationForm.get('configuration.getCustomerIdFromOriginatorEntity').enable({emitEvent: false});
this.targetNotificationForm.get('configuration.getCustomerIdFromOriginatorEntity').updateValueAndValidity({onlySelf: true});
this.targetNotificationForm.get('configuration.usersFilter.getCustomerIdFromOriginatorEntity').enable({onlySelf: true});
break;
}
this.targetNotificationForm.get('configuration.type').enable({emitEvent: false});
this.targetNotificationForm.get('configuration.description').enable({emitEvent: false});
this.targetNotificationForm.get('configuration.usersFilter.type').enable({emitEvent: false});
});
this.targetNotificationForm.get('configuration.getCustomerIdFromOriginatorEntity').valueChanges.pipe(
this.targetNotificationForm.get('configuration.usersFilter.getCustomerIdFromOriginatorEntity').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value: boolean) => {
if (value) {
this.targetNotificationForm.get('configuration.customerId').disable({emitEvent: false});
this.targetNotificationForm.get('configuration.usersFilter.customerId').disable({emitEvent: false});
} else {
this.targetNotificationForm.get('configuration.customerId').enable({emitEvent: false});
this.targetNotificationForm.get('configuration.usersFilter.customerId').enable({emitEvent: false});
}
});
if (isDefined(data.target)) {
this.targetNotificationForm.patchValue(data.target, {emitEvent: false});
this.targetNotificationForm.get('configuration.type').updateValueAndValidity({onlySelf: true});
this.targetNotificationForm.get('type').updateValueAndValidity({onlySelf: true});
this.targetNotificationForm.get('configuration.usersFilter.type').updateValueAndValidity({onlySelf: true});
}
}
@ -124,6 +131,7 @@ export class TargetNotificationDialogComponent extends
if (isDefined(this.data.target)) {
formValue = Object.assign({}, this.data.target, formValue);
}
formValue.configuration.type = formValue.type;
this.notificationService.saveNotificationTarget(formValue).subscribe(
(target) => this.dialogRef.close(target)
);

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

@ -131,13 +131,21 @@ export interface NonConfirmedNotificationEscalation {
export interface NotificationTarget extends Omit<BaseData<NotificationTargetId>, 'label'>{
tenantId: TenantId;
type: NotificationTargetType;
configuration: NotificationTargetConfig;
}
export interface NotificationTargetConfig extends
export interface NotificationTargetConfig extends Partial<PlatformUsersNotificationTargetConfig & SlackNotificationTargetConfig> {
description?: string;
type: NotificationTargetType;
}
export interface PlatformUsersNotificationTargetConfig {
usersFilter: UsersFilter;
}
export interface UsersFilter extends
Partial<UserListNotificationTargetConfig & CustomerUsersNotificationTargetConfig>{
type: NotificationTargetConfigType;
description?: string;
}
interface UserListNotificationTargetConfig {
@ -149,6 +157,21 @@ interface CustomerUsersNotificationTargetConfig {
getCustomerIdFromOriginatorEntity: boolean;
}
export interface SlackNotificationTargetConfig {
conversationType: SlackConversation;
conversationId: string;
conversationName: string;
}
export enum NotificationTargetType {
PLATFORM_USERS = 'PLATFORM_USERS',
SLACK = 'SLACK'
}
export const NotificationTargetTypeTranslationMap = new Map<NotificationTargetType, string>([
[NotificationTargetType.PLATFORM_USERS, 'notification.platform-users'],
[NotificationTargetType.SLACK, 'notification.slack']
]);
export interface NotificationTemplate extends Omit<BaseData<NotificationTemplateId>, 'label'>{
tenantId: TenantId;
notificationType: NotificationType;

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

@ -2745,6 +2745,7 @@
"no-rules-notification": "No rules notification",
"rule": "Rule",
"rules": "Rules",
"platform-users": "Platform users",
"search-notification": "Search notification",
"search-targets": "Search recipients",
"search-templates": "Search templates",
@ -2753,6 +2754,7 @@
"send-test-notification": "Send test notification",
"sent": "Sent",
"settings": "Notification settings",
"slack": "Slack",
"slack-chanel-type": "Slack chanel type",
"slack-chanel-types": {
"direct": "Direct message",

Loading…
Cancel
Save