72 changed files with 844 additions and 342 deletions
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* 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.service.executors; |
||||
|
|
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.common.util.AbstractListeningExecutor; |
||||
|
|
||||
|
@Component |
||||
|
public class NotificationExecutorService extends AbstractListeningExecutor { |
||||
|
|
||||
|
@Override |
||||
|
protected int getThreadPollSize() { |
||||
|
return 10; // FIXME [viacheslav]
|
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,168 @@ |
|||||
|
/** |
||||
|
* 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.service.notification; |
||||
|
|
||||
|
import com.fasterxml.jackson.core.type.TypeReference; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.test.web.servlet.ResultActions; |
||||
|
import org.springframework.test.web.servlet.ResultMatcher; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.notification.targets.AllUsersNotificationTargetConfig; |
||||
|
import org.thingsboard.server.common.data.notification.targets.CustomerUsersNotificationTargetConfig; |
||||
|
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; |
||||
|
import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig; |
||||
|
import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.controller.AbstractControllerTest; |
||||
|
import org.thingsboard.server.dao.notification.NotificationTargetDao; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
|
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
|
||||
|
@DaoSqlTest |
||||
|
public class NotificationTargetApiTest extends AbstractControllerTest { |
||||
|
|
||||
|
@Autowired |
||||
|
private NotificationTargetDao notificationTargetDao; |
||||
|
|
||||
|
@Before |
||||
|
public void beforeEach() throws Exception { |
||||
|
loginTenantAdmin(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenInvalidNotificationTarget_whenSaving_returnValidationError() throws Exception { |
||||
|
NotificationTarget notificationTarget = new NotificationTarget(); |
||||
|
notificationTarget.setTenantId(null); |
||||
|
notificationTarget.setName(null); |
||||
|
notificationTarget.setConfiguration(null); |
||||
|
|
||||
|
String validationError = saveAndGetError(notificationTarget, status().isBadRequest()); |
||||
|
assertThat(validationError) |
||||
|
.contains("name must not be") |
||||
|
.contains("configuration must not be"); |
||||
|
|
||||
|
SingleUserNotificationTargetConfig singleUserConfig = new SingleUserNotificationTargetConfig(); |
||||
|
singleUserConfig.setUserId(null); |
||||
|
notificationTarget.setConfiguration(singleUserConfig); |
||||
|
|
||||
|
validationError = saveAndGetError(notificationTarget, status().isBadRequest()); |
||||
|
assertThat(validationError) |
||||
|
.contains("userId must not be"); |
||||
|
|
||||
|
UserListNotificationTargetConfig userListConfig = new UserListNotificationTargetConfig(); |
||||
|
userListConfig.setUsersIds(Collections.emptyList()); |
||||
|
notificationTarget.setConfiguration(userListConfig); |
||||
|
|
||||
|
validationError = saveAndGetError(notificationTarget, status().isBadRequest()); |
||||
|
assertThat(validationError) |
||||
|
.contains("usersIds must not be"); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenNotificationTargetWithUsersFromDifferentTenant_whenSaving_returnAccessDeniedError() throws Exception { |
||||
|
loginDifferentTenant(); |
||||
|
NotificationTarget notificationTarget = new NotificationTarget(); |
||||
|
notificationTarget.setTenantId(differentTenantId); |
||||
|
notificationTarget.setName("Target 1"); |
||||
|
UserListNotificationTargetConfig userListConfig = new UserListNotificationTargetConfig(); |
||||
|
userListConfig.setUsersIds(List.of(customerUserId.getId(), tenantAdminUserId.getId())); |
||||
|
notificationTarget.setConfiguration(userListConfig); |
||||
|
|
||||
|
saveAndGetError(notificationTarget, status().isForbidden()); |
||||
|
|
||||
|
SingleUserNotificationTargetConfig singleUserConfig = new SingleUserNotificationTargetConfig(); |
||||
|
singleUserConfig.setUserId(customerUserId.getId()); |
||||
|
notificationTarget.setConfiguration(singleUserConfig); |
||||
|
|
||||
|
saveAndGetError(notificationTarget, status().isForbidden()); |
||||
|
|
||||
|
loginSysAdmin(); |
||||
|
notificationTarget.setTenantId(TenantId.SYS_TENANT_ID); |
||||
|
notificationTarget.setConfiguration(userListConfig); |
||||
|
save(notificationTarget, status().isOk()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenNotificationTargetConfig_testGetRecipients() throws Exception { |
||||
|
NotificationTarget notificationTarget = new NotificationTarget(); |
||||
|
notificationTarget.setTenantId(tenantId); |
||||
|
notificationTarget.setName("Test target"); |
||||
|
CustomerUsersNotificationTargetConfig customerUsersConfig = new CustomerUsersNotificationTargetConfig(); |
||||
|
customerUsersConfig.setCustomerId(customerId.getId()); |
||||
|
notificationTarget.setConfiguration(customerUsersConfig); |
||||
|
|
||||
|
List<User> recipients = getRecipients(notificationTarget); |
||||
|
assertThat(recipients).size().isNotZero(); |
||||
|
assertThat(recipients).allSatisfy(recipient -> { |
||||
|
assertThat(recipient.getCustomerId()).isEqualTo(customerId); |
||||
|
}); |
||||
|
|
||||
|
AllUsersNotificationTargetConfig allUsersConfig = new AllUsersNotificationTargetConfig(); |
||||
|
notificationTarget.setConfiguration(allUsersConfig); |
||||
|
recipients = getRecipients(notificationTarget); |
||||
|
assertThat(recipients).size().isGreaterThanOrEqualTo(2); |
||||
|
assertThat(recipients).allSatisfy(recipient -> { |
||||
|
assertThat(recipient.getTenantId()).isEqualTo(tenantId); |
||||
|
}); |
||||
|
|
||||
|
createDifferentTenant(); |
||||
|
loginSysAdmin(); |
||||
|
recipients = getRecipients(notificationTarget); |
||||
|
assertThat(recipients).size().isGreaterThanOrEqualTo(3); |
||||
|
assertThat(recipients).anySatisfy(recipient -> { |
||||
|
assertThat(recipient.getTenantId()).isEqualTo(tenantId); |
||||
|
}); |
||||
|
assertThat(recipients).anySatisfy(recipient -> { |
||||
|
assertThat(recipient.getTenantId()).isEqualTo(differentTenantId); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void whenDeletingTenant_thenDeleteNotificationTarget() throws Exception { |
||||
|
createDifferentTenant(); |
||||
|
NotificationTarget notificationTarget = new NotificationTarget(); |
||||
|
notificationTarget.setName("Test 1"); |
||||
|
notificationTarget.setTenantId(differentTenantId); |
||||
|
notificationTarget.setConfiguration(new AllUsersNotificationTargetConfig()); |
||||
|
save(notificationTarget, status().isOk()); |
||||
|
assertThat(notificationTargetDao.find(TenantId.SYS_TENANT_ID)).isNotEmpty(); |
||||
|
|
||||
|
deleteDifferentTenant(); |
||||
|
assertThat(notificationTargetDao.find(TenantId.SYS_TENANT_ID)).isEmpty(); |
||||
|
} |
||||
|
|
||||
|
private String saveAndGetError(NotificationTarget notificationTarget, ResultMatcher statusMatcher) throws Exception { |
||||
|
return getErrorMessage(save(notificationTarget, statusMatcher)); |
||||
|
} |
||||
|
|
||||
|
private ResultActions save(NotificationTarget notificationTarget, ResultMatcher statusMatcher) throws Exception { |
||||
|
return doPost("/api/notification/target", notificationTarget) |
||||
|
.andExpect(statusMatcher); |
||||
|
} |
||||
|
|
||||
|
private List<User> getRecipients(NotificationTarget notificationTarget) throws Exception { |
||||
|
return doPostWithTypedResponse("/api/notification/target/recipients?page=0&pageSize=100", notificationTarget, new TypeReference<PageData<User>>() {}).getData(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
/** |
||||
|
* 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.service.notification; |
||||
|
|
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.springframework.test.web.servlet.ResultActions; |
||||
|
import org.springframework.test.web.servlet.ResultMatcher; |
||||
|
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; |
||||
|
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; |
||||
|
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; |
||||
|
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; |
||||
|
import org.thingsboard.server.controller.AbstractControllerTest; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
|
||||
|
@DaoSqlTest |
||||
|
public class NotificationTemplateApiTest extends AbstractControllerTest { |
||||
|
|
||||
|
@Before |
||||
|
public void beforeEach() throws Exception { |
||||
|
loginTenantAdmin(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenInvalidNotificationTemplate_whenSaving_returnValidationError() throws Exception { |
||||
|
NotificationTemplate notificationTemplate = new NotificationTemplate(); |
||||
|
notificationTemplate.setTenantId(tenantId); |
||||
|
notificationTemplate.setName(null); |
||||
|
notificationTemplate.setNotificationType(null); |
||||
|
notificationTemplate.setConfiguration(null); |
||||
|
|
||||
|
String validationError = saveAndGetError(notificationTemplate, status().isBadRequest()); |
||||
|
assertThat(validationError) |
||||
|
.contains("name must not be") |
||||
|
.contains("notificationType must not be") |
||||
|
.contains("configuration must not be"); |
||||
|
|
||||
|
NotificationTemplateConfig config = new NotificationTemplateConfig(); |
||||
|
notificationTemplate.setConfiguration(config); |
||||
|
config.setDefaultTextTemplate("Default text"); |
||||
|
EmailDeliveryMethodNotificationTemplate emailTemplate = new EmailDeliveryMethodNotificationTemplate(); |
||||
|
emailTemplate.setMethod(NotificationDeliveryMethod.EMAIL); |
||||
|
emailTemplate.setBody(null); |
||||
|
emailTemplate.setSubject(null); |
||||
|
config.setTemplates(Map.of( |
||||
|
NotificationDeliveryMethod.EMAIL, emailTemplate |
||||
|
)); |
||||
|
notificationTemplate.setName("<script/>"); |
||||
|
|
||||
|
validationError = saveAndGetError(notificationTemplate, status().isBadRequest()); |
||||
|
assertThat(validationError) |
||||
|
.doesNotContain("defaultTextTemplate must be specified") |
||||
|
.contains("subject must not be") |
||||
|
.contains("name is malformed"); |
||||
|
|
||||
|
config.setDefaultTextTemplate(null); |
||||
|
|
||||
|
validationError = saveAndGetError(notificationTemplate, status().isBadRequest()); |
||||
|
assertThat(validationError) |
||||
|
.contains("defaultTextTemplate must be specified"); |
||||
|
} |
||||
|
|
||||
|
private String saveAndGetError(NotificationTemplate notificationTemplate, ResultMatcher statusMatcher) throws Exception { |
||||
|
return getErrorMessage(save(notificationTemplate, statusMatcher)); |
||||
|
} |
||||
|
|
||||
|
private ResultActions save(NotificationTemplate notificationTemplate, ResultMatcher statusMatcher) throws Exception { |
||||
|
return doPost("/api/notification/template", notificationTemplate) |
||||
|
.andExpect(statusMatcher); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,86 +0,0 @@ |
|||||
/** |
|
||||
* 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.service.notification; |
|
||||
|
|
||||
import com.google.common.base.Strings; |
|
||||
import org.apache.commons.lang3.StringUtils; |
|
||||
import org.thingsboard.rest.client.RestClient; |
|
||||
import org.thingsboard.server.common.data.notification.AlarmOriginatedNotificationInfo; |
|
||||
import org.thingsboard.server.common.data.notification.Notification; |
|
||||
import org.thingsboard.server.common.data.notification.NotificationInfo; |
|
||||
import org.thingsboard.server.common.data.notification.NotificationOriginatorType; |
|
||||
|
|
||||
import java.text.SimpleDateFormat; |
|
||||
import java.util.Date; |
|
||||
import java.util.List; |
|
||||
import java.util.Scanner; |
|
||||
|
|
||||
public class NotificationsClient extends NotificationApiWsClient { |
|
||||
|
|
||||
private NotificationsClient(String wsUrl, String token) throws Exception { |
|
||||
super(wsUrl, token); |
|
||||
} |
|
||||
|
|
||||
public static NotificationsClient newInstance(String username, String password) throws Exception { |
|
||||
RestClient restClient = new RestClient("http://localhost:8080"); |
|
||||
restClient.login(username, password); |
|
||||
NotificationsClient client = new NotificationsClient("ws://localhost:8080", restClient.getToken()); |
|
||||
client.connectBlocking(); |
|
||||
return client; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onMessage(String s) { |
|
||||
super.onMessage(s); |
|
||||
// printNotificationsCount();
|
|
||||
printNotifications(); |
|
||||
} |
|
||||
|
|
||||
public void printNotifications() { |
|
||||
System.out.println(StringUtils.repeat(System.lineSeparator(), 20)); |
|
||||
List<Notification> notifications = getNotifications(); |
|
||||
System.out.printf(" %s NEW MESSAGE%s\n\n", getUnreadCount(), notifications.size() > 1 ? "S" : ""); |
|
||||
notifications.forEach(notification -> { |
|
||||
String notificationInfoStr = ""; |
|
||||
if (notification.getOriginatorType() == NotificationOriginatorType.ALARM) { |
|
||||
AlarmOriginatedNotificationInfo info = (AlarmOriginatedNotificationInfo) notification.getInfo(); |
|
||||
notificationInfoStr = String.format("Alarm of type %s - %s severity - status: %s", |
|
||||
info.getAlarmType(), info.getAlarmSeverity(), info.getAlarmStatus()); |
|
||||
} else if (notification.getInfo() != null) { |
|
||||
notificationInfoStr = Strings.nullToEmpty(notification.getInfo().getDescription()); |
|
||||
} |
|
||||
SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); |
|
||||
String time = format.format(new Date(notification.getCreatedTime())); |
|
||||
// System.out.printf("[%s] %-19s | %-30s | (%s)\n", time, notification.getReason(), notification.getText(), notificationInfoStr);
|
|
||||
}); |
|
||||
System.out.println(StringUtils.repeat(System.lineSeparator(), 5)); |
|
||||
} |
|
||||
|
|
||||
public void printNotificationsCount() { |
|
||||
System.out.println(); |
|
||||
System.out.println(); |
|
||||
System.out.println(); |
|
||||
int unreadCount = getUnreadCount(); |
|
||||
System.out.printf("\r\r%s NEW MESSAGE%s", unreadCount, unreadCount > 1 ? "S" : ""); |
|
||||
} |
|
||||
|
|
||||
public static void main(String[] args) throws Exception { |
|
||||
NotificationsClient client = NotificationsClient.newInstance("tenant@thingsboard.org", "tenant"); |
|
||||
client.subscribeForUnreadNotifications(5); |
|
||||
// client.subscribeForUnreadNotificationsCount();
|
|
||||
new Scanner(System.in).nextLine(); |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue