Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard into cf-output

pull/14535/head
IrynaMatveieva 10 months ago
parent
commit
c948c63acb
  1. 43
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java
  2. 22
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/HasLatestTs.java
  3. 9
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/TsRollingArgumentEntry.java
  4. 18
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesArgumentEntry.java
  5. 11
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingArgumentEntry.java
  6. 1
      application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java
  7. 85
      application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java
  8. 289
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/JavaRestClientTest.java
  9. 42
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

43
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java

@ -26,8 +26,6 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
import org.thingsboard.server.service.cf.ctx.state.aggregation.RelatedEntitiesArgumentEntry;
import org.thingsboard.server.service.cf.ctx.state.aggregation.single.EntityAggregationArgumentEntry;
import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingArgumentEntry;
import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingZoneState;
import org.thingsboard.server.utils.CalculatedFieldUtils;
import java.io.Closeable;
@ -41,7 +39,7 @@ import java.util.stream.Collectors;
@Getter
public abstract class BaseCalculatedFieldState implements CalculatedFieldState, Closeable {
protected static final long DEFAULT_LAST_UPDATE_TS = -1L;
public static final long DEFAULT_LAST_UPDATE_TS = -1L;
protected final EntityId entityId;
protected CalculatedFieldCtx ctx;
@ -103,7 +101,6 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState,
updatedArguments = new HashMap<>(argumentValues.size());
}
updatedArguments.put(key, newEntry);
updateLastUpdateTimestamp(newEntry);
}
}
@ -161,23 +158,29 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState,
return resultNode;
}
private void updateLastUpdateTimestamp(ArgumentEntry entry) {
long newTs = this.latestTimestamp;
if (entry instanceof SingleValueArgumentEntry singleValueArgumentEntry) {
newTs = singleValueArgumentEntry.getTs();
} else if (entry instanceof TsRollingArgumentEntry tsRollingArgumentEntry) {
Map.Entry<Long, Double> lastEntry = tsRollingArgumentEntry.getTsRecords().lastEntry();
newTs = (lastEntry != null) ? lastEntry.getKey() : DEFAULT_LAST_UPDATE_TS;
} else if (entry instanceof RelatedEntitiesArgumentEntry relatedEntitiesArgumentEntry) {
newTs = relatedEntitiesArgumentEntry.getEntityInputs().values().stream()
.mapToLong(e -> (e instanceof SingleValueArgumentEntry s) ? s.getTs() : DEFAULT_LAST_UPDATE_TS)
.max()
.orElse(DEFAULT_LAST_UPDATE_TS);
} else if (entry instanceof GeofencingArgumentEntry geofencingArgumentEntry) {
newTs = geofencingArgumentEntry.getZoneStates().values().stream()
.mapToLong(GeofencingZoneState::getTs).max().orElse(DEFAULT_LAST_UPDATE_TS);
public long getLatestTimestamp() {
long latestTs = DEFAULT_LAST_UPDATE_TS;
boolean allDefault = arguments.values().stream().allMatch(entry -> {
if (entry instanceof SingleValueArgumentEntry single) {
return single.isDefaultValue();
}
return false;
});
for (ArgumentEntry entry : arguments.values()) {
if (entry instanceof SingleValueArgumentEntry single) {
if (allDefault) {
latestTs = Math.max(latestTs, single.getTs());
} else if (!single.isDefaultValue()) {
latestTs = Math.max(latestTs, single.getTs());
}
} else if (entry instanceof HasLatestTs hasLatestTsEntry) {
latestTs = Math.max(latestTs, hasLatestTsEntry.getLatestTs());
}
}
this.latestTimestamp = Math.max(this.latestTimestamp, newTs);
return latestTs;
}
protected ReadinessStatus checkReadiness(List<String> requiredArguments, Map<String, ArgumentEntry> currentArguments) {

22
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/HasLatestTs.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2025 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.cf.ctx.state;
public interface HasLatestTs {
long getLatestTs();
}

9
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/TsRollingArgumentEntry.java

@ -31,11 +31,13 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import static org.thingsboard.server.service.cf.ctx.state.BaseCalculatedFieldState.DEFAULT_LAST_UPDATE_TS;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
public class TsRollingArgumentEntry implements ArgumentEntry {
public class TsRollingArgumentEntry implements ArgumentEntry, HasLatestTs {
private Integer limit;
private Long timeWindow;
@ -83,6 +85,11 @@ public class TsRollingArgumentEntry implements ArgumentEntry {
return tsRecords;
}
public long getLatestTs() {
var lastEntry = tsRecords.lastEntry();
return (lastEntry != null) ? lastEntry.getKey() : DEFAULT_LAST_UPDATE_TS;
}
@Override
public TbelCfArg toTbelCfArg() {
List<TbelCfTsDoubleVal> values = new ArrayList<>(tsRecords.size());

18
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesArgumentEntry.java

@ -23,14 +23,17 @@ import org.thingsboard.script.api.tbel.TbelCfSingleValueArg;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
import org.thingsboard.server.service.cf.ctx.state.ArgumentEntryType;
import org.thingsboard.server.service.cf.ctx.state.HasLatestTs;
import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry;
import java.util.Map;
import java.util.stream.Collectors;
import static org.thingsboard.server.service.cf.ctx.state.BaseCalculatedFieldState.DEFAULT_LAST_UPDATE_TS;
@Data
@AllArgsConstructor
public class RelatedEntitiesArgumentEntry implements ArgumentEntry {
public class RelatedEntitiesArgumentEntry implements ArgumentEntry, HasLatestTs {
private final Map<EntityId, ArgumentEntry> entityInputs;
@ -46,6 +49,19 @@ public class RelatedEntitiesArgumentEntry implements ArgumentEntry {
return entityInputs;
}
@Override
public long getLatestTs() {
long latestTs = DEFAULT_LAST_UPDATE_TS;
for (ArgumentEntry entry : entityInputs.values()) {
if (entry instanceof SingleValueArgumentEntry single) {
if (!single.isDefaultValue()) {
latestTs = Math.max(latestTs, single.getTs());
}
}
}
return latestTs;
}
@Override
public boolean updateEntry(ArgumentEntry entry) {
if (entry instanceof RelatedEntitiesArgumentEntry relatedEntitiesArgumentEntry) {

11
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingArgumentEntry.java

@ -25,13 +25,16 @@ import org.thingsboard.server.common.util.ProtoUtils;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
import org.thingsboard.server.service.cf.ctx.state.ArgumentEntryType;
import org.thingsboard.server.service.cf.ctx.state.HasLatestTs;
import java.util.Map;
import java.util.stream.Collectors;
import static org.thingsboard.server.service.cf.ctx.state.BaseCalculatedFieldState.DEFAULT_LAST_UPDATE_TS;
@Data
@Slf4j
public class GeofencingArgumentEntry implements ArgumentEntry {
public class GeofencingArgumentEntry implements ArgumentEntry, HasLatestTs {
private Map<EntityId, GeofencingZoneState> zoneStates;
@ -58,6 +61,12 @@ public class GeofencingArgumentEntry implements ArgumentEntry {
return zoneStates;
}
@Override
public long getLatestTs() {
return zoneStates.values().stream()
.mapToLong(GeofencingZoneState::getTs).max().orElse(DEFAULT_LAST_UPDATE_TS);
}
@Override
public boolean updateEntry(ArgumentEntry entry) {
if (!(entry instanceof GeofencingArgumentEntry geofencingArgumentEntry)) {

1
application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java

@ -104,7 +104,6 @@ public class CalculatedFieldArgumentUtils {
public static TsKvEntry createDefaultTsKvEntry(Argument argument, long ts) {
return new BasicTsKvEntry(ts, createDefaultKvEntry(argument), DEFAULT_VERSION);
}
public static AttributeKvEntry createDefaultAttributeEntry(Argument argument, long ts) {
return new BaseAttributeKvEntry(createDefaultKvEntry(argument), ts, DEFAULT_VERSION);
}

85
application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java

@ -580,6 +580,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
@Test
public void testScriptCalculatedFieldWhenUsedLatestTsInScript() throws Exception {
Device testDevice = createDevice("Test device", "1234567890");
long ts = System.currentTimeMillis() - 300000L;
postTelemetry(testDevice.getId(), String.format("{\"ts\": %s, \"values\": {\"temperature\":30}}", ts));
@ -614,6 +615,90 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
});
}
@Test
public void testSimpleCalculatedFieldWhenUseLatestTsIsTrueAndDefaultArguments() throws Exception {
Device testDevice = createDevice("Test device", "1234567890");
CalculatedField calculatedField = new CalculatedField();
calculatedField.setEntityId(testDevice.getId());
calculatedField.setType(CalculatedFieldType.SIMPLE);
calculatedField.setName("a + b + c");
calculatedField.setDebugSettings(DebugSettings.all());
calculatedField.setConfigurationVersion(1);
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();
Argument argument1 = new Argument();
ReferencedEntityKey refEntityKey1 = new ReferencedEntityKey("a", ArgumentType.TS_LATEST, null);
argument1.setRefEntityKey(refEntityKey1);
argument1.setDefaultValue("100");
Argument argument2 = new Argument();
ReferencedEntityKey refEntityKey2 = new ReferencedEntityKey("b", ArgumentType.TS_LATEST, null);
argument2.setRefEntityKey(refEntityKey2);
argument2.setDefaultValue("200");
Argument argument3 = new Argument();
ReferencedEntityKey refEntityKey3 = new ReferencedEntityKey("c", ArgumentType.TS_LATEST, null);
argument3.setRefEntityKey(refEntityKey3);
argument3.setDefaultValue("300");
config.setArguments(Map.of("a", argument1, "b", argument2, "c", argument3));
config.setExpression("a + b + c");
TimeSeriesOutput output = new TimeSeriesOutput();
output.setName("d");
output.setDecimalsByDefault(0);
config.setOutput(output);
config.setUseLatestTs(true);
calculatedField.setConfiguration(config);
CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class);
await().alias("create CF -> perform initial calculation with default arguments").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ObjectNode d = getLatestTelemetry(testDevice.getId(), "d");
assertThat(d).isNotNull();
assertThat(d.get("d").get(0).get("value").asText()).isEqualTo("600");
});
doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"a\":10}"));
await().alias("update telemetry -> save result with ts of 'a' argument").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ObjectNode keys = getLatestTelemetry(testDevice.getId(), "d", "a");
assertThat(keys).isNotNull();
String aTs = keys.get("a").get(0).get("ts").asText();
assertThat(keys.get("d").get(0).get("ts").asText()).isEqualTo(aTs);
assertThat(keys.get("d").get(0).get("value").asText()).isEqualTo("510");
});
doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"b\":20}"));
doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"c\":30}"));
await().alias("update telemetry -> save result with latest ts of updated arguments").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ObjectNode keys = getLatestTelemetry(testDevice.getId(), "d");
assertThat(keys).isNotNull();
assertThat(keys.get("d").get(0).get("value").asText()).isEqualTo("60");
});
String latestTs = getLatestTelemetry(testDevice.getId(), "d").get("d").get(0).get("ts").asText();
doDelete("/api/plugins/telemetry/DEVICE/" + testDevice.getId() + "/timeseries/delete?keys=b&deleteAllDataForKeys=true").andExpect(status().isOk());
await().alias("delete telemetry -> save result with previous latest ts and default argument").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ObjectNode keys = getLatestTelemetry(testDevice.getId(), "d");
assertThat(keys).isNotNull();
assertThat(keys.get("d").get(0).get("ts").asText()).isEqualTo(latestTs);
assertThat(keys.get("d").get(0).get("value").asText()).isEqualTo("240");
});
}
@Test
public void testSimpleCalculatedFieldWhenCtxBecameUninitialized() throws Exception {
Device testDevice = createDevice("Test device", "1234567890");

289
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/JavaRestClientTest.java

@ -15,10 +15,11 @@
*/
package org.thingsboard.server.msa.connectivity;
import com.google.gson.JsonObject;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.HostnameVerificationPolicy;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
@ -30,26 +31,83 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rest.client.RestClient;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.domain.Domain;
import org.thingsboard.server.common.data.domain.DomainInfo;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.mobile.app.MobileApp;
import org.thingsboard.server.common.data.mobile.app.MobileAppStatus;
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundle;
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundleInfo;
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.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestInfo;
import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
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.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.HasSubject;
import org.thingsboard.server.common.data.notification.template.MobileAppDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.query.AvailableEntityKeys;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.TestProperties;
import javax.net.ssl.SSLContext;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.EMAIL;
import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.MICROSOFT_TEAMS;
import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.WEB;
import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevicePrototype;
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultTenantAdmin;
public class JavaRestClientTest extends AbstractContainerTest {
public static final String DEFAULT_NOTIFICATION_SUBJECT = "Just a test";
public static final NotificationType DEFAULT_NOTIFICATION_TYPE = NotificationType.GENERAL;
private RestClient restClient;
private Tenant tenant;
private User user;
@BeforeClass
public void beforeClass() throws Exception {
@ -77,11 +135,25 @@ public class JavaRestClientTest extends AbstractContainerTest {
@BeforeMethod
public void setUp() throws Exception {
restClient.login("tenant@thingsboard.org", "tenant");
restClient.login("sysadmin@thingsboard.org", "sysadmin");
// create tenant and tenant admin
tenant = new Tenant();
tenant.setTitle("Java Rest Client Test Tenant " + RandomStringUtils.randomAlphabetic(5));
tenant = restClient.saveTenant(tenant);
String email = RandomStringUtils.randomAlphabetic(5) + "@gmail.com";
user = restClient.saveUser(defaultTenantAdmin(tenant.getId(), email), false);
restClient.activateUser(user.getId(), "password123", false);
restClient.login(email, "password123");
}
@AfterMethod
public void tearDown() {
restClient.login("sysadmin@thingsboard.org", "sysadmin");
if (tenant != null) {
restClient.deleteTenant(tenant.getId());
}
}
@Test
@ -123,6 +195,219 @@ public class JavaRestClientTest extends AbstractContainerTest {
PageData<AlarmInfo> allClearedAlarms = restClient.getAllAlarms(AlarmSearchStatus.CLEARED, null, new TimePageLink(10, 0), null);
assertThat(allClearedAlarms.getData()).hasSize(0);
}
@Test
public void testTimeSeriesByReadTsKvQueries() {
Device device = restClient.saveDevice(defaultDevicePrototype(RandomStringUtils.randomAlphabetic(5)));
assertThat(device).isNotNull();
DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get();
for (int i = 0; i < 3; i++) {
JsonObject values = new JsonObject();
values.addProperty("temperature", i + 25);
testRestClient.postTelemetry(deviceCredentials.getCredentialsId(), JacksonUtil.toJsonNode(createPayload().toString()));
}
restClient.saveEntityTelemetry(device.getId(), "ts", JacksonUtil.toJsonNode("{\"temperature\": 25, \"humidity\": 60}"));
restClient.saveEntityTelemetry(device.getId(), "ts", JacksonUtil.toJsonNode("{\"temperature\": 27, \"humidity\": 59}"));
restClient.saveEntityTelemetry(device.getId(), "ts", JacksonUtil.toJsonNode("{\"temperature\": 33, \"humidity\": 62}"));
EntityTypeFilter filter = new EntityTypeFilter();
filter.setEntityType(EntityType.DEVICE);
var pageLink = new EntityDataPageLink(20, 0, null, new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.DESC), false);
var entityFields = Arrays.asList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"));
EntityDataQuery entityDataQuery = new EntityDataQuery(filter, pageLink, entityFields, null, null);
AvailableEntityKeys availableEntityKeys = restClient.findAvailableEntityKeysByQuery(entityDataQuery, true, true, null);
assertThat(availableEntityKeys).isNotNull();
assertThat(availableEntityKeys.timeseries()).contains("temperature", "humidity");
}
@Test
public void testFindNotifications() {
NotificationTarget notificationTarget = createNotificationTarget(user.getId());
String notificationText1 = "Notification 1";
NotificationTemplate notificationTemplate = createNotificationTemplate(DEFAULT_NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_SUBJECT, notificationText1, new NotificationDeliveryMethod[]{WEB});
NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), notificationTemplate.getId());
String notificationText2 = "Notification 2";
NotificationTemplate notificationTemplate2 = createNotificationTemplate(DEFAULT_NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_SUBJECT, notificationText2, new NotificationDeliveryMethod[]{WEB});
NotificationRequest notificationRequest2 = submitNotificationRequest(notificationTarget.getId(), notificationTemplate2.getId());
PageData<NotificationRequestInfo> initialRequests = restClient.getNotificationRequests(new PageLink(30));
assertThat(initialRequests.getTotalElements()).isGreaterThanOrEqualTo(2);
NotificationRequestInfo notificationRequestInfo = restClient.getNotificationRequestById(notificationRequest.getId()).get();
assertThat(notificationRequestInfo.getName()).isEqualTo(notificationRequest.getName());
assertThat(notificationRequestInfo.getTemplateName()).isEqualTo(notificationTemplate.getName());
NotificationRequestPreview requestPreview = restClient.getNotificationRequestPreview(notificationRequest, 10);
assertThat(requestPreview.getTotalRecipientsCount()).isEqualTo(1);
assertThat(requestPreview.getRecipientsPreview()).isEqualTo(List.of(user.getEmail()));
PageData<Notification> notifications = restClient.getNotifications(false, WEB, new PageLink(30));
assertThat(notifications.getTotalElements()).isEqualTo(2);
Integer unreadCount = restClient.getUnreadNotificationsCount(WEB);
assertThat(unreadCount).isEqualTo(2);
restClient.markNotificationAsRead(notifications.getData().get(0).getId());
Integer unreadCountAfterRead = restClient.getUnreadNotificationsCount(WEB);
assertThat(unreadCountAfterRead).isEqualTo(1);
restClient.markAllNotificationsAsRead(WEB);
Integer unreadCountAfterAllRead = restClient.getUnreadNotificationsCount(WEB);
assertThat(unreadCountAfterAllRead).isEqualTo(0);
restClient.deleteNotification(notifications.getData().get(0).getId());
notifications = restClient.getNotifications(false, WEB, new PageLink(30));
assertThat(notifications.getTotalElements()).isEqualTo(1);
restClient.deleteNotificationRequest(notificationRequest.getId());
PageData<NotificationRequestInfo> requestsAfterUpdate = restClient.getNotificationRequests(new PageLink(30));
assertThat(requestsAfterUpdate.getTotalElements()).isEqualTo(initialRequests.getTotalElements() - 1);
List<NotificationDeliveryMethod> availableDeliveryMethods = restClient.getAvailableDeliveryMethods();
assertThat(availableDeliveryMethods).contains(WEB, EMAIL, MICROSOFT_TEAMS);
}
@Test
public void testSaveNotificationSettings() {
NotificationSettings settings = new NotificationSettings();
SlackNotificationDeliveryMethodConfig slackConfig = new SlackNotificationDeliveryMethodConfig();
String slackToken = "xoxb-123123123";
slackConfig.setBotToken(slackToken);
settings.setDeliveryMethodsConfigs(Map.of(
NotificationDeliveryMethod.SLACK, slackConfig
));
restClient.saveNotificationSettings(settings);
NotificationSettings savedSettings = restClient.getNotificationSettings().get();
assertThat(savedSettings.getDeliveryMethodsConfigs()).hasSize(1);
assertThat(savedSettings.getDeliveryMethodsConfigs().get(slackConfig.getMethod())).isEqualTo(slackConfig);
// save user notification settings
var entityActionNotificationPref = new UserNotificationSettings.NotificationPref();
entityActionNotificationPref.setEnabled(true);
entityActionNotificationPref.setEnabledDeliveryMethods(Map.of(
NotificationDeliveryMethod.WEB, true,
NotificationDeliveryMethod.SMS, false,
NotificationDeliveryMethod.EMAIL, false
));
UserNotificationSettings userNotificationSettings = new UserNotificationSettings(Map.of(
NotificationType.ENTITY_ACTION, entityActionNotificationPref
));
UserNotificationSettings saved = restClient.saveUserNotificationSettings(userNotificationSettings);
UserNotificationSettings retrieved = restClient.getUserNotificationSettings().get();
assertThat(retrieved).isEqualTo(saved);
}
@Test
public void testSaveDomain() {
restClient.login("sysadmin@thingsboard.org", "sysadmin");
Domain domain = new Domain();
String prefix = RandomStringUtils.randomAlphabetic(5).toLowerCase();
domain.setName(prefix + ".test.com");
Domain savedDomain = restClient.saveDomain(domain);
assertThat(savedDomain.getName()).isEqualTo(domain.getName());
PageData<DomainInfo> domainInfos = restClient.getTenantDomainInfos(new PageLink(10, 0 , prefix));
assertThat(domainInfos.getData()).hasSize(1);
}
@Test
public void testSaveMobileApp() {
restClient.login("sysadmin@thingsboard.org", "sysadmin");
MobileApp mobileApp = new MobileApp();
String prefix = RandomStringUtils.randomAlphabetic(5).toLowerCase();
mobileApp.setPkgName(prefix + "test.app.apple");
mobileApp.setPlatformType(PlatformType.ANDROID);
mobileApp.setAppSecret(RandomStringUtils.randomAlphabetic(20));
mobileApp.setStatus(MobileAppStatus.DRAFT);
MobileApp savedMobileApp = restClient.saveMobileApp(mobileApp);
assertThat(savedMobileApp.getName()).isEqualTo(mobileApp.getName());
PageData<MobileApp> retrieved = restClient.getTenantMobileApps(new PageLink(10, 0, prefix));
assertThat(retrieved.getData()).hasSize(1);
MobileAppBundle mobileAppBundle = new MobileAppBundle();
String bundlePrefix = RandomStringUtils.randomAlphabetic(5).toLowerCase();
mobileAppBundle.setTitle(bundlePrefix + "Test Bundle");
mobileAppBundle.setAndroidAppId(savedMobileApp.getId());
MobileAppBundle savedMobileAppBundle = restClient.saveMobileBundle(mobileAppBundle);
PageData<MobileAppBundleInfo> bundleInfos = restClient.getTenantMobileBundleInfos(new PageLink(10, 0, bundlePrefix));
assertThat(bundleInfos.getData()).hasSize(1);
}
private NotificationTarget createNotificationTarget(UserId... usersIds) {
UserListFilter filter = new UserListFilter();
filter.setUsersIds(Arrays.stream(usersIds).map(UUIDBased::getId).toList());
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setName(filter.toString() + org.apache.commons.lang3.RandomStringUtils.randomNumeric(5));
PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
targetConfig.setUsersFilter(filter);
notificationTarget.setConfiguration(targetConfig);
return restClient.saveNotificationTarget(notificationTarget);
}
private NotificationTemplate createNotificationTemplate(NotificationType notificationType, String subject,
String text, NotificationDeliveryMethod... deliveryMethods) {
NotificationTemplate notificationTemplate = new NotificationTemplate();
notificationTemplate.setName("Notification template: " + RandomStringUtils.randomAlphabetic(5));
notificationTemplate.setNotificationType(notificationType);
NotificationTemplateConfig config = new NotificationTemplateConfig();
config.setDeliveryMethodsTemplates(new HashMap<>());
for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
DeliveryMethodNotificationTemplate deliveryMethodNotificationTemplate;
switch (deliveryMethod) {
case WEB: {
deliveryMethodNotificationTemplate = new WebDeliveryMethodNotificationTemplate();
break;
}
case EMAIL: {
deliveryMethodNotificationTemplate = new EmailDeliveryMethodNotificationTemplate();
break;
}
case SMS: {
deliveryMethodNotificationTemplate = new SmsDeliveryMethodNotificationTemplate();
break;
}
case MOBILE_APP:
deliveryMethodNotificationTemplate = new MobileAppDeliveryMethodNotificationTemplate();
break;
default:
throw new IllegalArgumentException("Unsupported delivery method " + deliveryMethod);
}
deliveryMethodNotificationTemplate.setEnabled(true);
deliveryMethodNotificationTemplate.setBody(text);
if (deliveryMethodNotificationTemplate instanceof HasSubject) {
((HasSubject) deliveryMethodNotificationTemplate).setSubject(subject);
}
config.getDeliveryMethodsTemplates().put(deliveryMethod, deliveryMethodNotificationTemplate);
}
notificationTemplate.setConfiguration(config);
return restClient.saveNotificationTemplate(notificationTemplate);
}
private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, NotificationTemplateId notificationTemplateId) {
NotificationRequestConfig config = new NotificationRequestConfig();
config.setSendingDelayInSec(0);
NotificationRequest notificationRequest = NotificationRequest.builder()
.targets(List.of(targetId).stream().map(UUIDBased::getId).collect(Collectors.toList()))
.templateId(notificationTemplateId)
.additionalConfig(config)
.build();
return restClient.saveNotificationRequest(notificationRequest);
}
}

42
rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

@ -146,6 +146,8 @@ 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.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo;
@ -2294,7 +2296,8 @@ public class RestClient implements Closeable {
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<DomainInfo>>() {
}).getBody();
},
params).getBody();
}
public Optional<DomainInfo> getDomainInfoById(DomainId domainId) {
@ -2330,7 +2333,8 @@ public class RestClient implements Closeable {
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<MobileApp>>() {
}).getBody();
},
params).getBody();
}
public Optional<MobileApp> getMobileAppById(MobileAppId mobileAppId) {
@ -2362,7 +2366,8 @@ public class RestClient implements Closeable {
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<MobileAppBundleInfo>>() {
}).getBody();
},
params).getBody();
}
public Optional<MobileAppBundle> getMobileBundleById(MobileAppBundleId mobileAppBundleId) {
@ -4289,11 +4294,23 @@ public class RestClient implements Closeable {
}
}
public PageData<Notification> getNotifications(PageLink pageLink) {
public PageData<Notification> getNotifications(Boolean unreadOnly, NotificationDeliveryMethod deliveryMethod, PageLink pageLink) {
Map<String, String> params = new HashMap<>();
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.append(baseURL).append("/api/notifications?").append(getUrlParams(pageLink));
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/notifications?" + getUrlParams(pageLink),
if (unreadOnly != null) {
urlBuilder.append("&unreadOnly={unreadOnly}");
params.put("unreadOnly", unreadOnly.toString());
}
if (deliveryMethod != null) {
urlBuilder.append("&deliveryMethod={deliveryMethod}");
params.put("deliveryMethod", deliveryMethod.name());
}
return restTemplate.exchange(urlBuilder.toString(),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<Notification>>() {
@ -4334,7 +4351,8 @@ public class RestClient implements Closeable {
baseURL + uri,
HttpMethod.PUT,
HttpEntity.EMPTY,
Void.class);
Void.class,
params);
}
@ -4342,7 +4360,7 @@ public class RestClient implements Closeable {
restTemplate.delete(baseURL + "/api/notification/{id}", notificationId.getId());
}
public NotificationRequest createNotificationRequest(NotificationRequest notificationRequest) {
public NotificationRequest saveNotificationRequest(NotificationRequest notificationRequest) {
return restTemplate.postForEntity(baseURL + "/api/notification/request", notificationRequest, NotificationRequest.class).getBody();
}
@ -4421,6 +4439,14 @@ public class RestClient implements Closeable {
}
}
public NotificationTarget saveNotificationTarget(NotificationTarget notificationTarget) {
return restTemplate.postForEntity(baseURL + "/api/notification/target", notificationTarget, NotificationTarget.class).getBody();
}
public NotificationTemplate saveNotificationTemplate(NotificationTemplate notificationTemplate) {
return restTemplate.postForEntity(baseURL + "/api/notification/template", notificationTemplate, NotificationTemplate.class).getBody();
}
public AiModel saveAiModel(AiModel aiModel) {
return restTemplate.postForEntity(baseURL + "/api/ai/model", aiModel, AiModel.class).getBody();
}

Loading…
Cancel
Save