Browse Source

Merge pull request #8793 from thingsboard/feature/account-notification-settings

User-level notification settings
pull/9091/head
Andrew Shvayka 3 years ago
committed by GitHub
parent
commit
18a617472a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  2. 13
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  3. 4
      application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
  4. 70
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  5. 12
      application/src/test/java/org/thingsboard/server/service/notification/TestNotificationSettingsService.java
  6. 2
      application/src/test/resources/application-test.properties
  7. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java
  8. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java
  9. 82
      common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/UserNotificationSettings.java
  10. 2
      common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java
  11. 2
      common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java
  12. 64
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java
  13. 9
      ui-ngx/src/app/core/http/notification.service.ts
  14. 6
      ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts
  15. 8
      ui-ngx/src/app/modules/home/pages/notification/notification.module.ts
  16. 40
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.html
  17. 24
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.scss
  18. 126
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.ts
  19. 41
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings-routing.modules.ts
  20. 84
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.html
  21. 41
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.scss
  22. 188
      ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.ts
  23. 9
      ui-ngx/src/app/shared/models/notification.models.ts
  24. 15
      ui-ngx/src/assets/locale/locale.constant-en_US.json

20
application/src/main/java/org/thingsboard/server/controller/NotificationController.java

@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestInfo;
import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
@ -297,7 +298,7 @@ public class NotificationController extends BaseController {
if (targetType == NotificationTargetType.PLATFORM_USERS) {
PageData<User> recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(),
(PlatformUsersNotificationTargetConfig) target.getConfiguration(), new PageLink(recipientsPreviewSize, 0, null,
new SortOrder("createdTime", SortOrder.Direction.DESC)));
SortOrder.BY_CREATED_TIME_DESC));
recipientsCount = (int) recipients.getTotalElements();
recipientsPart = recipients.getData().stream().map(r -> (NotificationRecipient) r).collect(Collectors.toList());
} else {
@ -431,10 +432,23 @@ public class NotificationController extends BaseController {
notes = "Returns the list of delivery methods that are properly configured and are allowed to be used for sending notifications." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/notification/deliveryMethods")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
accessControlService.checkPermission(user, Resource.ADMIN_SETTINGS, Operation.READ);
return notificationCenter.getAvailableDeliveryMethods(user.getTenantId());
}
@PostMapping("/notification/settings/user")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public UserNotificationSettings saveUserNotificationSettings(@RequestBody @Valid UserNotificationSettings settings,
@AuthenticationPrincipal SecurityUser user) {
return notificationSettingsService.saveUserNotificationSettings(user.getTenantId(), user.getId(), settings);
}
@GetMapping("/notification/settings/user")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public UserNotificationSettings getUserNotificationSettings(@AuthenticationPrincipal SecurityUser user) {
return notificationSettingsService.getUserNotificationSettings(user.getTenantId(), user.getId(), true);
}
}

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

@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
@ -237,7 +238,17 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
private void processForRecipient(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient, NotificationProcessingContext ctx) throws Exception {
if (ctx.getStats().contains(deliveryMethod, recipient.getId())) {
throw new AlreadySentException();
} else {
ctx.getStats().reportProcessed(deliveryMethod, recipient.getId());
}
if (recipient instanceof User) {
UserNotificationSettings settings = notificationSettingsService.getUserNotificationSettings(ctx.getTenantId(), ((User) recipient).getId(), false);
if (!settings.isEnabled(ctx.getNotificationType(), deliveryMethod)) {
throw new RuntimeException("User disabled " + deliveryMethod.getName() + " notifications of this type");
}
}
NotificationChannel notificationChannel = channels.get(deliveryMethod);
DeliveryMethodNotificationTemplate processedTemplate = ctx.getProcessedTemplate(deliveryMethod, recipient);
@ -251,7 +262,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
Notification notification = Notification.builder()
.requestId(request.getId())
.recipientId(recipient.getId())
.type(ctx.getNotificationTemplate().getNotificationType())
.type(ctx.getNotificationType())
.subject(processedTemplate.getSubject())
.text(processedTemplate.getBody())
.additionalConfig(processedTemplate.getAdditionalConfig())

4
application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
@ -52,6 +53,8 @@ public class NotificationProcessingContext {
private final Set<NotificationDeliveryMethod> deliveryMethods;
@Getter
private final NotificationTemplate notificationTemplate;
@Getter
private final NotificationType notificationType;
private final Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates;
@Getter
@ -65,6 +68,7 @@ public class NotificationProcessingContext {
this.deliveryMethods = deliveryMethods;
this.settings = settings;
this.notificationTemplate = template;
this.notificationType = template.getNotificationType();
this.templates = new EnumMap<>(NotificationDeliveryMethod.class);
this.stats = new NotificationRequestStats();
init();

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

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.notification;
import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.data.Offset;
import org.java_websocket.client.WebSocketClient;
@ -23,6 +24,7 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
@ -35,6 +37,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
@ -59,6 +62,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
@ -470,6 +474,66 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(1);
}
@Test
public void testUserNotificationSettings() throws Exception {
var entityActionNotificationPref = new UserNotificationSettings.NotificationPref();
entityActionNotificationPref.setEnabled(true);
entityActionNotificationPref.setEnabledDeliveryMethods(Map.of(
NotificationDeliveryMethod.WEB, true,
NotificationDeliveryMethod.SMS, false,
NotificationDeliveryMethod.EMAIL, false
));
var entitiesLimitNotificationPref = new UserNotificationSettings.NotificationPref();
entitiesLimitNotificationPref.setEnabled(true);
entitiesLimitNotificationPref.setEnabledDeliveryMethods(Map.of(
NotificationDeliveryMethod.SMS, true,
NotificationDeliveryMethod.WEB, false,
NotificationDeliveryMethod.EMAIL, false
));
var apiUsageLimitNotificationPref = new UserNotificationSettings.NotificationPref();
apiUsageLimitNotificationPref.setEnabled(false);
apiUsageLimitNotificationPref.setEnabledDeliveryMethods(Map.of(
NotificationDeliveryMethod.WEB, true,
NotificationDeliveryMethod.SMS, false,
NotificationDeliveryMethod.EMAIL, false
));
UserNotificationSettings settings = new UserNotificationSettings(Map.of(
NotificationType.ENTITY_ACTION, entityActionNotificationPref,
NotificationType.ENTITIES_LIMIT, entitiesLimitNotificationPref,
NotificationType.API_USAGE_LIMIT, apiUsageLimitNotificationPref
));
doPost("/api/notification/settings/user", settings, UserNotificationSettings.class);
var entityActionNotificationTemplate = createNotificationTemplate(NotificationType.ENTITY_ACTION, "Entity action", "Entity action", NotificationDeliveryMethod.WEB);
var entitiesLimitNotificationTemplate = createNotificationTemplate(NotificationType.ENTITIES_LIMIT, "Entities limit", "Entities limit", NotificationDeliveryMethod.WEB);
var apiUsageLimitNotificationTemplate = createNotificationTemplate(NotificationType.API_USAGE_LIMIT, "API usage limit", "API usage limit", NotificationDeliveryMethod.WEB);
NotificationTarget target = createNotificationTarget(tenantAdminUserId);
NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId)
.templateId(entityActionNotificationTemplate.getId())
.originatorEntityId(tenantAdminUserId)
.targets(List.of(target.getUuidId()))
.ruleId(new NotificationRuleId(UUID.randomUUID())) // to trigger user settings check
.build();
NotificationRequestStats stats = submitNotificationRequestAndWait(notificationRequest);
assertThat(stats.getErrors()).isEmpty();
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB).get()).isOne();
notificationRequest.setTemplateId(entitiesLimitNotificationTemplate.getId());
stats = submitNotificationRequestAndWait(notificationRequest);
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).matches(n -> n == null || n.get() == 0);
assertThat(stats.getErrors().get(NotificationDeliveryMethod.WEB).values()).first().asString().contains("disabled");
notificationRequest.setTemplateId(apiUsageLimitNotificationTemplate.getId());
stats = submitNotificationRequestAndWait(notificationRequest);
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).matches(n -> n == null || n.get() == 0);
assertThat(stats.getErrors().get(NotificationDeliveryMethod.WEB).values()).first().asString().contains("disabled");
}
@Test
public void testSlackNotifications() throws Exception {
NotificationSettings settings = new NotificationSettings();
@ -524,6 +588,12 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(stats.getErrors().get(NotificationDeliveryMethod.SLACK).values()).containsExactly(errorMessage);
}
private NotificationRequestStats submitNotificationRequestAndWait(NotificationRequest notificationRequest) throws Exception {
SettableFuture<NotificationRequestStats> future = SettableFuture.create();
notificationCenter.processNotificationRequest(notificationRequest.getTenantId(), notificationRequest, future::set);
return future.get(30, TimeUnit.SECONDS);
}
private void checkFullNotificationsUpdate(UnreadNotificationsUpdate notificationsUpdate, String... expectedNotifications) {
assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getText).containsOnly(expectedNotifications);
assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getType).containsOnly(DEFAULT_NOTIFICATION_TYPE);

12
application/src/test/java/org/thingsboard/server/service/notification/MockNotificationSettingsService.java → application/src/test/java/org/thingsboard/server/service/notification/TestNotificationSettingsService.java

@ -19,14 +19,20 @@ import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.notification.DefaultNotificationSettingsService;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.user.UserSettingsService;
@Service
@Primary
public class MockNotificationSettingsService extends DefaultNotificationSettingsService {
public class TestNotificationSettingsService extends DefaultNotificationSettingsService {
public MockNotificationSettingsService(AdminSettingsService adminSettingsService) {
super(adminSettingsService, null, null, null);
public TestNotificationSettingsService(AdminSettingsService adminSettingsService,
NotificationTargetService notificationTargetService,
NotificationTemplateService notificationTemplateService,
UserSettingsService userSettingsService) {
super(adminSettingsService, notificationTargetService, notificationTemplateService, null, userSettingsService);
}
@Override

2
application/src/test/resources/application-test.properties

@ -68,3 +68,5 @@ sql.ttl.audit_logs.ttl=2592000
sql.edge_events.partition_size=168
sql.ttl.edge_events.edge_event_ttl=2592000
server.log_controller_error_stack_trace=false

6
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java

@ -16,7 +16,9 @@
package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
public interface NotificationSettingsService {
@ -24,6 +26,10 @@ public interface NotificationSettingsService {
NotificationSettings findNotificationSettings(TenantId tenantId);
UserNotificationSettings saveUserNotificationSettings(TenantId tenantId, UserId userId, UserNotificationSettings settings);
UserNotificationSettings getUserNotificationSettings(TenantId tenantId, UserId userId, boolean format);
void createDefaultNotificationConfigs(TenantId tenantId);
void updateDefaultNotificationConfigs(TenantId tenantId);

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

@ -54,7 +54,6 @@ public class NotificationRequestStats {
public void reportSent(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient) {
sent.computeIfAbsent(deliveryMethod, k -> new AtomicInteger()).incrementAndGet();
processedRecipients.computeIfAbsent(deliveryMethod, k -> ConcurrentHashMap.newKeySet()).add(recipient.getId());
}
public void reportError(NotificationDeliveryMethod deliveryMethod, Throwable error, NotificationRecipient recipient) {
@ -68,6 +67,10 @@ public class NotificationRequestStats {
errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getTitle(), errorMessage);
}
public void reportProcessed(NotificationDeliveryMethod deliveryMethod, Object recipientId) {
processedRecipients.computeIfAbsent(deliveryMethod, k -> ConcurrentHashMap.newKeySet()).add(recipientId);
}
public boolean contains(NotificationDeliveryMethod deliveryMethod, Object recipientId) {
Set<Object> processedRecipients = this.processedRecipients.get(deliveryMethod);
return processedRecipients != null && processedRecipients.contains(recipientId);

82
common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/UserNotificationSettings.java

@ -0,0 +1,82 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.settings;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Data
public class UserNotificationSettings {
@NotNull
@Valid
private final Map<NotificationType, NotificationPref> prefs;
public static final UserNotificationSettings DEFAULT = new UserNotificationSettings(Collections.emptyMap());
public static final Set<NotificationDeliveryMethod> deliveryMethods = NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods();
@JsonCreator
public UserNotificationSettings(@JsonProperty("prefs") Map<NotificationType, NotificationPref> prefs) {
this.prefs = prefs;
}
public boolean isEnabled(NotificationType notificationType, NotificationDeliveryMethod deliveryMethod) {
NotificationPref pref = prefs.get(notificationType);
if (pref == null) {
return true;
}
if (!pref.isEnabled()) {
return false;
}
return pref.getEnabledDeliveryMethods().getOrDefault(deliveryMethod, true);
}
@Data
public static class NotificationPref {
private boolean enabled;
@NotNull
private Map<NotificationDeliveryMethod, Boolean> enabledDeliveryMethods;
public static NotificationPref createDefault() {
NotificationPref pref = new NotificationPref();
pref.setEnabled(true);
pref.setEnabledDeliveryMethods(deliveryMethods.stream().collect(Collectors.toMap(v -> v, v -> true)));
return pref;
}
@JsonIgnore
@AssertTrue(message = "Only email, Web and SMS delivery methods are allowed")
public boolean isValid() {
return enabledDeliveryMethods.entrySet().stream()
.allMatch(entry -> deliveryMethods.contains(entry.getKey()) && entry.getValue() != null);
}
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java

@ -36,4 +36,6 @@ public class SortOrder {
ASC, DESC
}
public static final SortOrder BY_CREATED_TIME_DESC = new SortOrder("createdTime", Direction.DESC);
}

2
common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettingsType.java

@ -19,7 +19,7 @@ import lombok.Getter;
public enum UserSettingsType {
GENERAL, VISITED_DASHBOARDS(true), QUICK_LINKS, DOC_LINKS, DASHBOARDS, GETTING_STARTED;
GENERAL, VISITED_DASHBOARDS(true), QUICK_LINKS, DOC_LINKS, DASHBOARDS, GETTING_STARTED, NOTIFICATIONS;
@Getter
private final boolean reserved;

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

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.notification;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@ -25,8 +26,11 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings.NotificationPref;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.AffectedTenantAdministratorsFilter;
import org.thingsboard.server.common.data.notification.targets.platform.AffectedUserFilter;
@ -38,20 +42,28 @@ import org.thingsboard.server.common.data.notification.targets.platform.TenantAd
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilterType;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.user.UserSettingsService;
import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
@RequiredArgsConstructor
@Slf4j
public class DefaultNotificationSettingsService implements NotificationSettingsService {
private final AdminSettingsService adminSettingsService;
private final NotificationTargetService notificationTargetService;
private final NotificationTemplateService notificationTemplateService;
private final DefaultNotifications defaultNotifications;
private final UserSettingsService userSettingsService;
private static final String SETTINGS_KEY = "notifications";
@ -81,6 +93,58 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
});
}
@Override
public UserNotificationSettings saveUserNotificationSettings(TenantId tenantId, UserId userId, UserNotificationSettings settings) {
UserSettings userSettings = new UserSettings();
userSettings.setUserId(userId);
userSettings.setType(UserSettingsType.NOTIFICATIONS);
userSettings.setSettings(JacksonUtil.valueToTree(settings));
userSettingsService.saveUserSettings(tenantId, userSettings);
return formatUserNotificationSettings(settings);
}
@Override
public UserNotificationSettings getUserNotificationSettings(TenantId tenantId, UserId userId, boolean format) {
UserSettings userSettings = userSettingsService.findUserSettings(tenantId, userId, UserSettingsType.NOTIFICATIONS);
UserNotificationSettings settings = null;
if (userSettings != null) {
try {
settings = JacksonUtil.treeToValue(userSettings.getSettings(), UserNotificationSettings.class);
} catch (Exception e) {
log.warn("Failed to parse notification settings for user {}", userId, e);
}
}
if (settings == null) {
settings = UserNotificationSettings.DEFAULT;
}
if (format) {
settings = formatUserNotificationSettings(settings);
}
return settings;
}
private UserNotificationSettings formatUserNotificationSettings(UserNotificationSettings settings) {
Map<NotificationType, NotificationPref> prefs = new EnumMap<>(NotificationType.class);
if (settings != null) {
prefs.putAll(settings.getPrefs());
}
NotificationPref defaultPref = NotificationPref.createDefault();
for (NotificationType notificationType : NotificationType.values()) {
NotificationPref pref = prefs.get(notificationType);
if (pref == null) {
prefs.put(notificationType, defaultPref);
} else {
var enabledDeliveryMethods = new LinkedHashMap<>(pref.getEnabledDeliveryMethods());
// in case a new delivery method was added to the platform
UserNotificationSettings.deliveryMethods.forEach(deliveryMethod -> {
enabledDeliveryMethods.putIfAbsent(deliveryMethod, true);
});
pref.setEnabledDeliveryMethods(enabledDeliveryMethods);
}
}
return new UserNotificationSettings(prefs);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED) // so that parent transaction is not aborted on method failure
@Override
public void createDefaultNotificationConfigs(TenantId tenantId) {

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

@ -31,6 +31,7 @@ import {
NotificationTarget,
NotificationTemplate,
NotificationType,
NotificationUserSettings,
SlackChanelType,
SlackConversation
} from '@shared/models/notification.models';
@ -174,4 +175,12 @@ export class NotificationService {
}
return this.http.get<PageData<NotificationTemplate>>(url, defaultHttpOptionsFromConfig(config));
}
public getNotificationUserSettings(config?: RequestConfig): Observable<NotificationUserSettings> {
return this.http.get<NotificationUserSettings>(`/api/notification/settings/user`, defaultHttpOptionsFromConfig(config));
}
public saveNotificationUserSettings(settings: NotificationUserSettings, config?: RequestConfig): Observable<NotificationUserSettings> {
return this.http.post<NotificationUserSettings>('/api/notification/settings/user', settings, defaultHttpOptionsFromConfig(config));
}
}

6
ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts

@ -23,6 +23,9 @@ import { profileRoutes } from '@home/pages/profile/profile-routing.module';
import { getCurrentAuthState } from '@core/auth/auth.selectors';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import {
notificationUserSettingsRoutes
} from '@home/pages/notification/settings/notification-settings-routing.modules';
const routes: Routes = [
{
@ -49,7 +52,8 @@ const routes: Routes = [
}
},
...profileRoutes,
...securityRoutes
...securityRoutes,
...notificationUserSettingsRoutes
]
}
];

8
ui-ngx/src/app/modules/home/pages/notification/notification.module.ts

@ -35,6 +35,10 @@ import { EscalationFormComponent } from '@home/pages/notification/rule/escalatio
import { EscalationsComponent } from '@home/pages/notification/rule/escalations.component';
import { RuleNotificationDialogComponent } from '@home/pages/notification/rule/rule-notification-dialog.component';
import { RuleTableHeaderComponent } from '@home/pages/notification/rule/rule-table-header.component';
import { NotificationSettingsComponent } from '@home/pages/notification/settings/notification-settings.component';
import {
NotificationSettingFormComponent
} from '@home/pages/notification/settings/notification-setting-form.component';
@NgModule({
declarations: [
@ -49,7 +53,9 @@ import { RuleTableHeaderComponent } from '@home/pages/notification/rule/rule-tab
EscalationFormComponent,
EscalationsComponent,
RuleNotificationDialogComponent,
RuleTableHeaderComponent
RuleTableHeaderComponent,
NotificationSettingsComponent,
NotificationSettingFormComponent
],
imports: [
CommonModule,

40
ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.html

@ -0,0 +1,40 @@
<!--
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.
-->
<form [formGroup]="notificationSettingsFormGroup" fxLayout="column">
<div fxLayout="row" style="height: 48px;">
<div fxFlex="50" fxLayoutAlign="start center">
<mat-checkbox color="primary"
[checked]="notificationSettingsFormGroup.get('enabled').value"
(click)="toggleEnabled()">
<span class="notification-type"
[ngClass]="{'notification-type-disabled': !notificationSettingsFormGroup.get('enabled').value}">
{{notificationTemplateTypeTranslateMap.get(notificationSettingsFormGroup.get('name').value)?.name | translate}}
</span>
</mat-checkbox>
</div>
<div fxFlex fxLayout="row" *ngFor="let deliveryMethods of deliveryMethods">
<div fxFlex fxLayoutAlign="start center">
<mat-checkbox color="primary"
[disabled]="!allowDeliveryMethods?.includes(deliveryMethods) || !notificationSettingsFormGroup.get('enabled').value"
[checked]="getChecked(deliveryMethods)"
(click)="toggleDeliviryMethod(deliveryMethods)"
></mat-checkbox>
</div>
</div>
</div>
</form>

24
ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.scss

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
.notification-type {
font-size: 14px;
&-disabled {
color: rgba(0, 0, 0, 0.38)
}
}
}

126
ui-ngx/src/app/modules/home/pages/notification/settings/notification-setting-form.component.ts

@ -0,0 +1,126 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { UtilsService } from '@core/services/utils.service';
import { isDefinedAndNotNull } from '@core/utils';
import { Subscription } from 'rxjs';
import {
NotificationDeliveryMethod,
NotificationTemplateTypeTranslateMap,
NotificationUserSetting
} from '@shared/models/notification.models';
@Component({
selector: 'tb-notification-setting-form',
templateUrl: './notification-setting-form.component.html',
styleUrls: ['./notification-setting-form.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NotificationSettingFormComponent),
multi: true
}
]
})
export class NotificationSettingFormComponent implements ControlValueAccessor, OnInit, OnDestroy {
@Input()
disabled: boolean;
@Input()
deliveryMethods: NotificationDeliveryMethod[] = [];
@Input()
allowDeliveryMethods: NotificationDeliveryMethod[] = [];
notificationSettingsFormGroup: UntypedFormGroup;
notificationTemplateTypeTranslateMap = NotificationTemplateTypeTranslateMap;
private propagateChange = null;
private valueChange$: Subscription = null;
constructor(private utils: UtilsService,
private fb: UntypedFormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
const deliveryMethod = {};
this.deliveryMethods.forEach(value => {
deliveryMethod[value] = true;
});
this.notificationSettingsFormGroup = this.fb.group(
{
name: [''],
enabled: [true],
enabledDeliveryMethods: this.fb.group({
...deliveryMethod
})
});
this.valueChange$ = this.notificationSettingsFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
ngOnDestroy() {
if (this.valueChange$) {
this.valueChange$.unsubscribe();
this.valueChange$ = null;
}
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.notificationSettingsFormGroup.disable({emitEvent: false});
} else {
this.notificationSettingsFormGroup.enable({emitEvent: false});
}
}
toggleEnabled() {
this.notificationSettingsFormGroup.get('enabled').patchValue(!this.notificationSettingsFormGroup.get('enabled').value);
}
getChecked(deliveryMethod: NotificationDeliveryMethod): boolean {
return this.notificationSettingsFormGroup.get('enabledDeliveryMethods').get(deliveryMethod).value;
}
toggleDeliviryMethod(deliveryMethod: NotificationDeliveryMethod) {
this.notificationSettingsFormGroup.get('enabledDeliveryMethods').get(deliveryMethod)
.patchValue(!this.notificationSettingsFormGroup.get('enabledDeliveryMethods').get(deliveryMethod).value);
}
writeValue(value: NotificationUserSetting): void {
if (isDefinedAndNotNull(value)) {
this.notificationSettingsFormGroup.patchValue(value, {emitEvent: false});
}
}
private updateModel() {
this.propagateChange(this.notificationSettingsFormGroup.value);
}
}

41
ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings-routing.modules.ts

@ -0,0 +1,41 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Routes } from '@angular/router';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { Authority } from '@shared/models/authority.enum';
import { inject, NgModule } from '@angular/core';
import { NotificationSettingsComponent } from '@home/pages/notification/settings/notification-settings.component';
import { NotificationService } from '@core/http/notification.service';
export const notificationUserSettingsRoutes: Routes = [
{
path: 'notificationSettings',
component: NotificationSettingsComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'account.notification-settings',
breadcrumb: {
label: 'account.notification-settings',
icon: 'settings'
}
},
resolve: {
userSettings: () => inject(NotificationService).getNotificationUserSettings()
}
}
];

84
ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.html

@ -0,0 +1,84 @@
<!--
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.
-->
<div>
<mat-card appearance="outlined" class="settings-card tb-absolute-fill">
<mat-card-header>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.xs="8px"
fxLayoutAlign="space-between start" fxLayoutAlign.xs="start start">
<div fxFlex fxLayout="column">
<span class="mat-headline-5" translate>notification.settings.notification-settings</span>
</div>
<div fxLayout="column">
<button mat-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="resetSettings()">{{ 'notification.settings.reset-all' | translate }}</button>
</div>
</div>
</mat-card-header>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content style="padding-top: 16px; overflow: hidden;">
<form [formGroup]="notificationSettings" class="notification-form">
<section class="notification-section">
<div class="notification-section-block">
<div fxLayout="row" fxLayoutAlign="start center" style="height: 44px;">
<div fxFlex="50">
<mat-checkbox color="warn"
(click)="$event.stopPropagation()"
[indeterminate]="getIndeterminate()"
(change)="changeInstanceTypeCheckBox($event.checked)"
[checked]="getChecked()">
<mat-label translate>notification.settings.type</mat-label>
</mat-checkbox>
</div>
<div fxFlex *ngFor="let deliveryMethods of notificationDeliveryMethods">
<div fxFlex fxLayoutAlign="start center">
<mat-checkbox color="warn"
[disabled]="!allowNotificationDeliveryMethods?.includes(deliveryMethods) || !getSomeChecked()"
[checked]="getChecked(deliveryMethods)"
(change)="changeInstanceTypeCheckBox($event.checked, deliveryMethods)"
[indeterminate]="getIndeterminate(deliveryMethods)"
(click)="$event.stopPropagation()">
<mat-label>{{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }}</mat-label>
</mat-checkbox>
</div>
</div>
</div>
<mat-divider></mat-divider>
<div *ngFor="let settingsControl of notificationSettingsFormArray.controls; let i = index; let $last = last;">
<tb-notification-setting-form [formControl]="settingsControl"
[deliveryMethods]="notificationDeliveryMethods"
[allowDeliveryMethods]="allowNotificationDeliveryMethods">
</tb-notification-setting-form>
<mat-divider *ngIf="!$last"></mat-divider>
</div>
</div>
</section>
</form>
</mat-card-content>
<div fxLayout="row" fxLayoutAlign="end start" style="padding: 16px;">
<button mat-button mat-raised-button color="primary"
type="button"
(click)="save()"
[disabled]="(isLoading$ | async) || notificationSettings.invalid || !notificationSettings.dirty">
{{ 'action.save' | translate }}
</button>
</div>
</mat-card>
</div>

41
ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.scss

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../../../../../../scss/constants";
:host {
.mat-mdc-card.settings-card {
margin: 8px;
@media #{$mat-gt-sm} {
width: 60%;
}
.mat-headline-5 {
margin: 0;
}
.notification-form {
height: 100%;
min-height: min-content;
max-height: min-content;
}
.notification-section {
height: 100%;
border: 1px solid rgba(0, 0, 0, 0.12);
overflow: scroll;
&-block {
min-width: 470px;
}
}
}
}

188
ui-ngx/src/app/modules/home/pages/notification/settings/notification-settings.component.ts

@ -0,0 +1,188 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
import { TranslateService } from '@ngx-translate/core';
import { ActivatedRoute } from '@angular/router';
import { deepClone, isDefinedAndNotNull } from '@core/utils';
import {
NotificationDeliveryMethod,
NotificationDeliveryMethodTranslateMap,
NotificationUserSettings
} from '@shared/models/notification.models';
import { NotificationService } from '@core/http/notification.service';
import { DialogService } from '@core/services/dialog.service';
@Component({
selector: 'tb-notification-settings',
templateUrl: './notification-settings.component.html',
styleUrls: ['./notification-settings.component.scss']
})
export class NotificationSettingsComponent extends PageComponent implements OnInit, HasConfirmForm {
notificationSettings: UntypedFormGroup;
notificationDeliveryMethods: NotificationDeliveryMethod[];
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
allowNotificationDeliveryMethods: Array<NotificationDeliveryMethod>;
constructor(protected store: Store<AppState>,
private route: ActivatedRoute,
private translate: TranslateService,
private dialogService: DialogService,
private notificationService: NotificationService,
private fb: UntypedFormBuilder,) {
super(store);
}
ngOnInit() {
this.notificationDeliveryMethods = this.getNotificationDeliveryMethods();
this.notificationService.getAvailableDeliveryMethods({ignoreLoading: true}).subscribe(allowMethods => {
this.allowNotificationDeliveryMethods = allowMethods;
});
this.buildNotificationSettingsForm();
this.patchNotificationSettings(this.route.snapshot.data.userSettings);
}
private getNotificationDeliveryMethods(): NotificationDeliveryMethod[] {
const deliveryMethods = new Set([
NotificationDeliveryMethod.SLACK
]);
return Object.values(NotificationDeliveryMethod).filter(type => !deliveryMethods.has(type));
}
private buildNotificationSettingsForm() {
this.notificationSettings = this.fb.group({
prefs: this.fb.array([])
});
}
private patchNotificationSettings(settings: NotificationUserSettings) {
const notificationSettingsControls: Array<AbstractControl> = [];
let preparedSettings;
if (settings.prefs) {
preparedSettings = this.prepareNotificationSettings(settings.prefs);
preparedSettings.forEach((setting) => {
setting.enabledDeliveryMethods = Object.assign(
this.notificationDeliveryMethods.reduce((a, v) => ({ ...a, [v]: true}), {}),
setting.enabledDeliveryMethods
);
notificationSettingsControls.push(this.fb.control(setting, [Validators.required]));
});
}
this.notificationSettings.setControl('prefs', this.fb.array(notificationSettingsControls), {emitEvent: false});
}
private prepareNotificationSettings(prefs: any) {
return Object.entries(prefs).map((value: any) => {
value[1].name = value[0];
return value[1];
});
}
resetSettings() {
this.dialogService.confirm(
this.translate.instant('notification.settings.reset-all-title'),
this.translate.instant('notification.settings.reset-all-text'),
this.translate.instant('action.no'),
this.translate.instant('action.yes'),
true
).subscribe(
result => {
if (result) {
const settings = this.prepareNotificationSettings(this.route.snapshot.data.userSettings.prefs);
const notificationSettingsControls: Array<AbstractControl> = [];
this.notificationSettings.reset({});
if (settings) {
settings.forEach((setting) => {
setting.enabled = true;
setting.enabledDeliveryMethods = this.notificationDeliveryMethods.reduce((a, v) => ({ ...a, [v]: true}), {});
notificationSettingsControls.push(this.fb.control(setting, [Validators.required]));
});
}
this.notificationSettings.setControl('prefs', this.fb.array(notificationSettingsControls), {emitEvent: false});
this.save();
}
}
);
}
getChecked = (method: NotificationDeliveryMethod = null): boolean => {
const type = this.notificationSettings.get('prefs').value;
if (isDefinedAndNotNull(method)) {
return isDefinedAndNotNull(type) && type.every(resource => resource.enabledDeliveryMethods[method]);
}
return isDefinedAndNotNull(type) && type.every(resource => resource.enabled);
};
getSomeChecked = () => {
const type = this.notificationSettings.get('prefs').value;
return isDefinedAndNotNull(type) && type.some(resource => resource.enabled);
};
getIndeterminate = (deliveryMethod: NotificationDeliveryMethod = null): boolean => {
const type = this.notificationSettings.get('prefs').value;
if (isDefinedAndNotNull(type)) {
const checkedResource = isDefinedAndNotNull(deliveryMethod) ?
type.filter(resource => resource.enabledDeliveryMethods[deliveryMethod]) :
type.filter(resource => resource.enabled);
return checkedResource.length !== 0 && checkedResource.length !== type.length;
}
return false;
};
changeInstanceTypeCheckBox = (value: boolean, deliveryMethod: NotificationDeliveryMethod = null): void => {
const type = deepClone(this.notificationSettings.get('prefs').value);
if (isDefinedAndNotNull(deliveryMethod)) {
type.forEach(notificationType => notificationType.enabledDeliveryMethods[deliveryMethod] = value);
} else {
type.forEach(notificationType => notificationType.enabled = value);
}
this.notificationSettings.get('prefs').patchValue(type);
this.notificationSettings.markAsDirty();
};
get notificationSettingsFormArray(): UntypedFormArray {
return this.notificationSettings.get('prefs') as UntypedFormArray;
}
save(): void {
const settings = {prefs: {}};
this.notificationSettings.getRawValue().prefs.forEach(value => {
const key = value.name;
delete value.name;
settings.prefs[key] = value;
});
this.notificationService.saveNotificationUserSettings(settings).subscribe(
(userSettings) => {
this.notificationSettings.get('prefs').reset({});
this.patchNotificationSettings(userSettings);
}
);
}
confirmForm(): UntypedFormGroup {
return this.notificationSettings;
}
}

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

@ -591,3 +591,12 @@ export const TriggerTypeTranslationMap = new Map<TriggerType, string>([
[TriggerType.NEW_PLATFORM_VERSION, 'notification.trigger.new-platform-version'],
[TriggerType.RATE_LIMITS, 'notification.trigger.rate-limits'],
]);
export interface NotificationUserSettings {
prefs: {[key: string]: NotificationUserSetting};
}
export interface NotificationUserSetting {
enabled: boolean;
enabledDeliveryMethods: {[key: string]: boolean};
}

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

@ -11,7 +11,8 @@
"permission-denied-text": "You don't have permission to perform this operation!"
},
"account": {
"account": "Account"
"account": "Account",
"notification-settings": "Notification settings"
},
"action": {
"activate": "Activate",
@ -3151,7 +3152,17 @@
"updated": "Updated",
"use-template": "Use template",
"view-all": "View all",
"warning": "Warning"
"warning": "Warning",
"settings": {
"notification-settings": "Notification settings",
"reset-all": "Reset all settings",
"reset-all-title": "Are you sure you want to reset form?",
"reset-all-text": "After the confirmation, the settings form will reset to the default value and save.",
"type": "Type",
"enable-all": "Enable all",
"disable-all": "Disable all",
"delivery-not-configured": "Delivery method is not configured"
}
},
"ota-update": {
"add": "Add package",

Loading…
Cancel
Save