Browse Source

Merge branch 'develop/3.5.2' into feature/widget-config

pull/8708/head
Igor Kulikov 3 years ago
parent
commit
7c6c7e5f67
  1. 37
      application/src/main/data/upgrade/3.5.1/schema_update.sql
  2. 13
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  3. 8
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  4. 3
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  5. 3
      application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
  6. 58
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java
  7. 11
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java
  8. 103
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java
  9. 55
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTargetExportService.java
  10. 42
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTemplateExportService.java
  11. 150
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java
  12. 109
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTargetImportService.java
  13. 72
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTemplateImportService.java
  14. 5
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  15. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java
  16. 108
      common/data/src/main/java/org/thingsboard/server/common/data/BaseDataWithAdditionalInfo.java
  17. 7
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  18. 6
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java
  19. 16
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java
  20. 17
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java
  21. 9
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java
  22. 8
      common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java
  23. 8
      dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
  24. 6
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
  25. 6
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  26. 6
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  27. 6
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  28. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  29. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  30. 7
      dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java
  31. 17
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java
  32. 6
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java
  33. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java
  34. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java
  35. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java
  36. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  37. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java
  38. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java
  39. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java
  40. 4
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java
  41. 6
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  42. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  43. 42
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
  44. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  45. 20
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java
  46. 20
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java
  47. 21
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java
  48. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java
  49. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java
  50. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java
  51. 44
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  52. 5
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java
  53. 13
      dao/src/main/resources/sql/schema-entities.sql
  54. 6
      dao/src/main/resources/sql/schema-views-and-functions.sql
  55. 30
      dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java
  56. 7
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
  57. 24
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java
  58. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CustomerPageElements.java
  59. 74
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java
  60. 42
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java
  61. 42
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabElements.java
  62. 30
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabHelper.java
  63. 97
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabElements.java
  64. 58
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabHelper.java
  65. 8
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java
  66. 193
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AssignToCustomerTest.java
  67. 97
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java
  68. 14
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeleteSeveralDevicesTest.java
  69. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesByNameAndTypeLoader.java
  70. 8
      ui-ngx/src/app/core/http/entity.service.ts
  71. 4
      ui-ngx/src/app/core/http/notification.service.ts
  72. 4
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html
  73. 2
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.ts
  74. 4
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html
  75. 13
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts
  76. 26
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
  77. 15
      ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts
  78. 1
      ui-ngx/src/app/shared/components/value-input.component.ts
  79. 6
      ui-ngx/src/app/shared/models/entity-type.models.ts
  80. 9
      ui-ngx/src/app/shared/models/notification.models.ts
  81. 11
      ui-ngx/src/app/shared/models/vc.models.ts
  82. 8
      ui-ngx/src/assets/locale/locale.constant-en_US.json

37
application/src/main/data/upgrade/3.5.1/schema_update.sql

@ -15,3 +15,40 @@
-- --
ALTER TABLE notification_rule ADD COLUMN IF NOT EXISTS enabled BOOLEAN NOT NULL DEFAULT true; ALTER TABLE notification_rule ADD COLUMN IF NOT EXISTS enabled BOOLEAN NOT NULL DEFAULT true;
-- NOTIFICATION CONFIGS VERSION CONTROL START
ALTER TABLE notification_template
ADD COLUMN IF NOT EXISTS external_id UUID;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_template_external_id') THEN
ALTER TABLE notification_template ADD CONSTRAINT uq_notification_template_external_id UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
ALTER TABLE notification_target
ADD COLUMN IF NOT EXISTS external_id UUID;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_target_external_id') THEN
ALTER TABLE notification_target ADD CONSTRAINT uq_notification_target_external_id UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
ALTER TABLE notification_rule
ADD COLUMN IF NOT EXISTS external_id UUID;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_rule_external_id') THEN
ALTER TABLE notification_rule ADD CONSTRAINT uq_notification_rule_external_id UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
-- NOTIFICATION CONFIGS VERSION CONTROL END

13
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -259,17 +259,16 @@ public class ThingsboardInstallService {
case "3.5.1": case "3.5.1":
log.info("Upgrading ThingsBoard from version 3.5.1 to 3.5.2 ..."); log.info("Upgrading ThingsBoard from version 3.5.1 to 3.5.2 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.5.1"); databaseEntitiesUpgradeService.upgradeDatabase("3.5.1");
entityDatabaseSchemaService.createOrUpdateViewsAndFunctions(); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
installScripts.loadSystemLwm2mResources();
break; break;
//TODO update CacheCleanupService on the next version upgrade
default: default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);
} }
entityDatabaseSchemaService.createOrUpdateViewsAndFunctions();
entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
installScripts.loadSystemLwm2mResources();
} }
log.info("Upgrade finished successfully!"); log.info("Upgrade finished successfully!");

8
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -720,6 +720,14 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
if (isOldSchema(conn, 3005000)) { if (isOldSchema(conn, 3005000)) {
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.5.1", SCHEMA_UPDATE_SQL); schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.5.1", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn); loadSql(schemaUpdateFile, conn);
try {
String[] entityNames = new String[]{"device"};
for (String entityName : entityNames) {
conn.createStatement().execute("ALTER TABLE " + entityName + " DROP COLUMN search_text CASCADE");
}
} catch (Exception e) {}
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005002;"); conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005002;");
} }
log.info("Schema updated."); log.info("Schema updated.");

3
application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java

@ -84,7 +84,8 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
break; break;
case "3.4.4": case "3.4.4":
log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0"); log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0");
clearCacheByName("deviceProfiles"); clearAll();
break;
default: default:
//Do nothing, since cache cleanup is optional. //Do nothing, since cache cleanup is optional.
} }

3
application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java

@ -67,7 +67,8 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
protected static final List<EntityType> SUPPORTED_ENTITY_TYPES = List.of( protected static final List<EntityType> SUPPORTED_ENTITY_TYPES = List.of(
EntityType.CUSTOMER, EntityType.ASSET_PROFILE, EntityType.ASSET, EntityType.RULE_CHAIN, EntityType.CUSTOMER, EntityType.ASSET_PROFILE, EntityType.ASSET, EntityType.RULE_CHAIN,
EntityType.DASHBOARD, EntityType.DEVICE_PROFILE, EntityType.DEVICE, EntityType.DASHBOARD, EntityType.DEVICE_PROFILE, EntityType.DEVICE,
EntityType.ENTITY_VIEW, EntityType.WIDGETS_BUNDLE EntityType.ENTITY_VIEW, EntityType.WIDGETS_BUNDLE,
EntityType.NOTIFICATION_TEMPLATE, EntityType.NOTIFICATION_TARGET, EntityType.NOTIFICATION_RULE
); );

58
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java

@ -22,36 +22,21 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao; import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.entity.EntityServiceRegistry;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.AccessControlService;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer;
@Service @Service
@TbCoreComponent @TbCoreComponent
@ -60,11 +45,10 @@ import java.util.function.BiConsumer;
public class DefaultExportableEntitiesService implements ExportableEntitiesService { public class DefaultExportableEntitiesService implements ExportableEntitiesService {
private final Map<EntityType, Dao<?>> daos = new HashMap<>(); private final Map<EntityType, Dao<?>> daos = new HashMap<>();
private final Map<EntityType, BiConsumer<TenantId, EntityId>> removers = new HashMap<>();
private final EntityServiceRegistry entityServiceRegistry;
private final AccessControlService accessControlService; private final AccessControlService accessControlService;
@Override @Override
public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId) { public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId) {
EntityType entityType = externalId.getEntityType(); EntityType entityType = externalId.getEntityType();
@ -152,11 +136,11 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
@Override @Override
public <I extends EntityId> void removeById(TenantId tenantId, I id) { public <I extends EntityId> void removeById(TenantId tenantId, I id) {
EntityType entityType = id.getEntityType(); EntityType entityType = id.getEntityType();
BiConsumer<TenantId, EntityId> entityRemover = removers.get(entityType); EntityDaoService entityService = entityServiceRegistry.getServiceByEntityType(entityType);
if (entityRemover == null) { if (entityService == null) {
throw new IllegalArgumentException("Unsupported entity type " + entityType); throw new IllegalArgumentException("Unsupported entity type " + entityType);
} }
entityRemover.accept(tenantId, id); entityService.deleteEntity(tenantId, id);
} }
private <I extends EntityId, E extends ExportableEntity<I>> ExportableEntityDao<I, E> getExportableEntityDao(EntityType entityType) { private <I extends EntityId, E extends ExportableEntity<I>> ExportableEntityDao<I, E> getExportableEntityDao(EntityType entityType) {
@ -182,34 +166,4 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
}); });
} }
@Autowired
private void setRemovers(CustomerService customerService, AssetService assetService, RuleChainService ruleChainService,
DashboardService dashboardService, DeviceProfileService deviceProfileService,
AssetProfileService assetProfileService, DeviceService deviceService, WidgetsBundleService widgetsBundleService) {
removers.put(EntityType.CUSTOMER, (tenantId, entityId) -> {
customerService.deleteCustomer(tenantId, (CustomerId) entityId);
});
removers.put(EntityType.ASSET, (tenantId, entityId) -> {
assetService.deleteAsset(tenantId, (AssetId) entityId);
});
removers.put(EntityType.RULE_CHAIN, (tenantId, entityId) -> {
ruleChainService.deleteRuleChainById(tenantId, (RuleChainId) entityId);
});
removers.put(EntityType.DASHBOARD, (tenantId, entityId) -> {
dashboardService.deleteDashboard(tenantId, (DashboardId) entityId);
});
removers.put(EntityType.DEVICE_PROFILE, (tenantId, entityId) -> {
deviceProfileService.deleteDeviceProfile(tenantId, (DeviceProfileId) entityId);
});
removers.put(EntityType.ASSET_PROFILE, (tenantId, entityId) -> {
assetProfileService.deleteAssetProfile(tenantId, (AssetProfileId) entityId);
});
removers.put(EntityType.DEVICE, (tenantId, entityId) -> {
deviceService.deleteDevice(tenantId, (DeviceId) entityId);
});
removers.put(EntityType.WIDGETS_BUNDLE, (tenantId, entityId) -> {
widgetsBundleService.deleteWidgetsBundle(tenantId, (WidgetsBundleId) entityId);
});
}
} }

11
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java

@ -24,7 +24,11 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Stream;
public abstract class BaseEntityExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> extends DefaultEntityExportService<I, E, D> { public abstract class BaseEntityExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> extends DefaultEntityExportService<I, E, D> {
@ -47,4 +51,11 @@ public abstract class BaseEntityExportService<I extends EntityId, E extends Expo
JacksonUtil.replaceUuidsRecursively(node, skipFieldsSet, uuid -> getExternalIdOrElseInternalByUuid(ctx, uuid)); JacksonUtil.replaceUuidsRecursively(node, skipFieldsSet, uuid -> getExternalIdOrElseInternalByUuid(ctx, uuid));
} }
protected Stream<UUID> toExternalIds(Collection<UUID> internalIds, Function<UUID, EntityId> entityIdCreator,
EntitiesExportCtx<?> ctx) {
return internalIds.stream().map(entityIdCreator)
.map(entityId -> getExternalIdOrElseInternal(ctx, entityId))
.map(EntityId::getId);
}
} }

103
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java

@ -0,0 +1,103 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.sync.ie.exporting.impl;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
public class NotificationRuleExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> extends BaseEntityExportService<NotificationRuleId, NotificationRule, EntityExportData<NotificationRule>> {
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, NotificationRule notificationRule, EntityExportData<NotificationRule> exportData) {
notificationRule.setTemplateId(getExternalIdOrElseInternal(ctx, notificationRule.getTemplateId()));
NotificationRuleTriggerConfig ruleTriggerConfig = notificationRule.getTriggerConfig();
switch (ruleTriggerConfig.getTriggerType()) {
case DEVICE_ACTIVITY: {
DeviceActivityNotificationRuleTriggerConfig triggerConfig = (DeviceActivityNotificationRuleTriggerConfig) ruleTriggerConfig;
Set<UUID> devices = triggerConfig.getDevices();
if (devices != null) {
triggerConfig.setDevices(toExternalIds(devices, DeviceId::new, ctx).collect(Collectors.toSet()));
}
Set<UUID> deviceProfiles = triggerConfig.getDeviceProfiles();
if (deviceProfiles != null) {
triggerConfig.setDeviceProfiles(toExternalIds(deviceProfiles, DeviceProfileId::new, ctx).collect(Collectors.toSet()));
}
break;
}
case RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT:
RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig = (RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig) ruleTriggerConfig;
Set<UUID> ruleChains = triggerConfig.getRuleChains();
if (ruleChains != null) {
triggerConfig.setRuleChains(toExternalIds(ruleChains, RuleChainId::new, ctx).collect(Collectors.toSet()));
}
break;
}
NotificationRuleRecipientsConfig ruleRecipientsConfig = notificationRule.getRecipientsConfig();
switch (ruleTriggerConfig.getTriggerType()) {
case ALARM: {
EscalatedNotificationRuleRecipientsConfig recipientsConfig = (EscalatedNotificationRuleRecipientsConfig) ruleRecipientsConfig;
Map<Integer, List<UUID>> escalationTable = new LinkedHashMap<>(recipientsConfig.getEscalationTable());
escalationTable.replaceAll((delay, targets) -> {
return toExternalIds(targets, NotificationTargetId::new, ctx).collect(Collectors.toList());
});
recipientsConfig.setEscalationTable(escalationTable);
break;
}
default: {
DefaultNotificationRuleRecipientsConfig recipientsConfig = (DefaultNotificationRuleRecipientsConfig) ruleRecipientsConfig;
List<UUID> targets = recipientsConfig.getTargets();
targets = toExternalIds(targets, NotificationTargetId::new, ctx).collect(Collectors.toList());
recipientsConfig.setTargets(targets);
break;
}
}
}
@Override
public Set<EntityType> getSupportedEntityTypes() {
return Set.of(EntityType.NOTIFICATION_RULE);
}
}

55
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTargetExportService.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.sync.ie.exporting.impl;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import java.util.Set;
@Service
@TbCoreComponent
public class NotificationTargetExportService extends BaseEntityExportService<NotificationTargetId, NotificationTarget, EntityExportData<NotificationTarget>> {
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, NotificationTarget notificationTarget, EntityExportData<NotificationTarget> exportData) {
if (notificationTarget.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) {
UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) notificationTarget.getConfiguration()).getUsersFilter();
switch (usersFilter.getType()) {
case CUSTOMER_USERS:
CustomerUsersFilter customerUsersFilter = (CustomerUsersFilter) usersFilter;
customerUsersFilter.setCustomerId(getExternalIdOrElseInternal(ctx, new CustomerId(customerUsersFilter.getCustomerId())).getId());
break;
}
}
}
@Override
public Set<EntityType> getSupportedEntityTypes() {
return Set.of(EntityType.NOTIFICATION_TARGET);
}
}

42
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTemplateExportService.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.sync.ie.exporting.impl;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import java.util.Set;
@Service
@TbCoreComponent
public class NotificationTemplateExportService extends BaseEntityExportService<NotificationTemplateId, NotificationTemplate, EntityExportData<NotificationTemplate>> {
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, NotificationTemplate notificationTemplate, EntityExportData<NotificationTemplate> exportData) {
}
@Override
public Set<EntityType> getSupportedEntityTypes() {
return Set.of(EntityType.NOTIFICATION_TEMPLATE);
}
}

150
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java

@ -0,0 +1,150 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class NotificationRuleImportService extends BaseEntityImportService<NotificationRuleId, NotificationRule, EntityExportData<NotificationRule>> {
private final NotificationRuleService notificationRuleService;
@Override
protected void setOwner(TenantId tenantId, NotificationRule notificationRule, IdProvider idProvider) {
notificationRule.setTenantId(tenantId);
}
@Override
protected NotificationRule prepare(EntitiesImportCtx ctx, NotificationRule notificationRule, NotificationRule oldNotificationRule, EntityExportData<NotificationRule> exportData, IdProvider idProvider) {
notificationRule.setTemplateId(idProvider.getInternalId(notificationRule.getTemplateId()));
NotificationRuleTriggerConfig ruleTriggerConfig = notificationRule.getTriggerConfig();
NotificationRuleTriggerType triggerType = ruleTriggerConfig.getTriggerType();
switch (triggerType) {
case DEVICE_ACTIVITY: {
DeviceActivityNotificationRuleTriggerConfig triggerConfig = (DeviceActivityNotificationRuleTriggerConfig) ruleTriggerConfig;
Set<UUID> devices = triggerConfig.getDevices();
if (devices != null) {
triggerConfig.setDevices(devices.stream().map(DeviceId::new)
.map(idProvider::getInternalId).map(UUIDBased::getId)
.collect(Collectors.toSet()));
}
Set<UUID> deviceProfiles = triggerConfig.getDeviceProfiles();
if (deviceProfiles != null) {
triggerConfig.setDeviceProfiles(deviceProfiles.stream().map(DeviceProfileId::new)
.map(idProvider::getInternalId).map(UUIDBased::getId)
.collect(Collectors.toSet()));
}
break;
}
case RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT:
RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig = (RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig) ruleTriggerConfig;
Set<UUID> ruleChains = triggerConfig.getRuleChains();
if (ruleChains != null) {
triggerConfig.setRuleChains(ruleChains.stream().map(RuleChainId::new)
.map(idProvider::getInternalId).map(UUIDBased::getId)
.collect(Collectors.toSet()));
}
break;
}
if (!triggerType.isTenantLevel()) {
throw new IllegalArgumentException("Trigger type " + triggerType + " is not available for tenants");
}
NotificationRuleRecipientsConfig ruleRecipientsConfig = notificationRule.getRecipientsConfig();
switch (triggerType) {
case ALARM: {
EscalatedNotificationRuleRecipientsConfig recipientsConfig = (EscalatedNotificationRuleRecipientsConfig) ruleRecipientsConfig;
Map<Integer, List<UUID>> escalationTable = new LinkedHashMap<>(recipientsConfig.getEscalationTable());
escalationTable.replaceAll((delay, targets) -> targets.stream()
.map(NotificationTargetId::new).map(idProvider::getInternalId)
.map(UUIDBased::getId).collect(Collectors.toList()));
recipientsConfig.setEscalationTable(escalationTable);
break;
}
default: {
DefaultNotificationRuleRecipientsConfig recipientsConfig = (DefaultNotificationRuleRecipientsConfig) ruleRecipientsConfig;
List<UUID> targets = recipientsConfig.getTargets().stream()
.map(NotificationTargetId::new).map(idProvider::getInternalId)
.map(UUIDBased::getId).collect(Collectors.toList());
recipientsConfig.setTargets(targets);
break;
}
}
return notificationRule;
}
@Override
protected NotificationRule saveOrUpdate(EntitiesImportCtx ctx, NotificationRule notificationRule, EntityExportData<NotificationRule> exportData, IdProvider idProvider) {
ConstraintValidator.validateFields(notificationRule);
return notificationRuleService.saveNotificationRule(ctx.getTenantId(), notificationRule);
}
@Override
protected void onEntitySaved(User user, NotificationRule savedEntity, NotificationRule oldEntity) throws ThingsboardException {
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, null,
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null);
clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedEntity.getId(),
oldEntity == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
}
@Override
protected NotificationRule deepCopy(NotificationRule notificationRule) {
return new NotificationRule(notificationRule);
}
@Override
public EntityType getEntityType() {
return EntityType.NOTIFICATION_RULE;
}
}

109
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTargetImportService.java

@ -0,0 +1,109 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.TenantAdministratorsFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class NotificationTargetImportService extends BaseEntityImportService<NotificationTargetId, NotificationTarget, EntityExportData<NotificationTarget>> {
private final NotificationTargetService notificationTargetService;
@Override
protected void setOwner(TenantId tenantId, NotificationTarget notificationTarget, IdProvider idProvider) {
notificationTarget.setTenantId(tenantId);
}
@Override
protected NotificationTarget prepare(EntitiesImportCtx ctx, NotificationTarget notificationTarget, NotificationTarget oldNotificationTarget, EntityExportData<NotificationTarget> exportData, IdProvider idProvider) {
if (notificationTarget.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) {
UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) notificationTarget.getConfiguration()).getUsersFilter();
switch (usersFilter.getType()) {
case CUSTOMER_USERS:
CustomerUsersFilter customerUsersFilter = (CustomerUsersFilter) usersFilter;
customerUsersFilter.setCustomerId(idProvider.getInternalId(new CustomerId(customerUsersFilter.getCustomerId())).getId());
break;
case USER_LIST:
UserListFilter userListFilter = (UserListFilter) usersFilter;
userListFilter.setUsersIds(userListFilter.getUsersIds().stream()
.map(UserId::new).map(idProvider::getInternalId)
.map(UUIDBased::getId).collect(Collectors.toList())
);
break;
case TENANT_ADMINISTRATORS:
if (CollectionUtils.isNotEmpty(((TenantAdministratorsFilter) usersFilter).getTenantsIds()) ||
CollectionUtils.isNotEmpty(((TenantAdministratorsFilter) usersFilter).getTenantProfilesIds())) {
throw new IllegalArgumentException("Permission denied");
}
break;
case SYSTEM_ADMINISTRATORS:
throw new AccessDeniedException("Permission denied");
}
}
return notificationTarget;
}
@Override
protected NotificationTarget saveOrUpdate(EntitiesImportCtx ctx, NotificationTarget notificationTarget, EntityExportData<NotificationTarget> exportData, IdProvider idProvider) {
ConstraintValidator.validateFields(notificationTarget);
return notificationTargetService.saveNotificationTarget(ctx.getTenantId(), notificationTarget);
}
@Override
protected void onEntitySaved(User user, NotificationTarget savedEntity, NotificationTarget oldEntity) throws ThingsboardException {
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, null,
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null);
}
@Override
protected NotificationTarget deepCopy(NotificationTarget notificationTarget) {
return new NotificationTarget(notificationTarget);
}
@Override
public EntityType getEntityType() {
return EntityType.NOTIFICATION_TARGET;
}
}

72
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTemplateImportService.java

@ -0,0 +1,72 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class NotificationTemplateImportService extends BaseEntityImportService<NotificationTemplateId, NotificationTemplate, EntityExportData<NotificationTemplate>> {
private final NotificationTemplateService notificationTemplateService;
@Override
protected void setOwner(TenantId tenantId, NotificationTemplate notificationTemplate, IdProvider idProvider) {
notificationTemplate.setTenantId(tenantId);
}
@Override
protected NotificationTemplate prepare(EntitiesImportCtx ctx, NotificationTemplate notificationTemplate, NotificationTemplate oldEntity, EntityExportData<NotificationTemplate> exportData, IdProvider idProvider) {
return notificationTemplate;
}
@Override
protected NotificationTemplate saveOrUpdate(EntitiesImportCtx ctx, NotificationTemplate notificationTemplate, EntityExportData<NotificationTemplate> exportData, IdProvider idProvider) {
ConstraintValidator.validateFields(notificationTemplate);
return notificationTemplateService.saveNotificationTemplate(ctx.getTenantId(), notificationTemplate);
}
@Override
protected void onEntitySaved(User user, NotificationTemplate savedEntity, NotificationTemplate oldEntity) throws ThingsboardException {
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, null,
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null);
}
@Override
protected NotificationTemplate deepCopy(NotificationTemplate notificationTemplate) {
return new NotificationTemplate(notificationTemplate);
}
@Override
public EntityType getEntityType() {
return EntityType.NOTIFICATION_TEMPLATE;
}
}

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

@ -480,7 +480,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
assertThat(getMyNotifications(false, 100)).size().isZero(); assertThat(getMyNotifications(false, 100)).size().isZero();
createDevice("Device 1", "default", "111"); createDevice("Device 1", "default", "111");
await().atMost(15, TimeUnit.SECONDS) await().atMost(30, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
assertThat(getMyNotifications(false, 100)).size().isEqualTo(1); assertThat(getMyNotifications(false, 100)).size().isEqualTo(1);
}); });
@ -494,9 +494,10 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
rule.setEnabled(true); rule.setEnabled(true);
saveNotificationRule(rule); saveNotificationRule(rule);
TimeUnit.SECONDS.sleep(2); // for rule update event to reach rules cache
createDevice("Device 3", "default", "333"); createDevice("Device 3", "default", "333");
await().atMost(15, TimeUnit.SECONDS) await().atMost(30, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
assertThat(getMyNotifications(false, 100)).size().isEqualTo(2); assertThat(getMyNotifications(false, 100)).size().isEqualTo(2);
}); });

4
common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java

@ -30,6 +30,10 @@ public interface EntityDaoService {
throw new IllegalArgumentException("Not implemented for " + getEntityType()); throw new IllegalArgumentException("Not implemented for " + getEntityType());
} }
default void deleteEntity(TenantId tenantId, EntityId id) {
throw new IllegalArgumentException(getEntityType().getNormalName() + " deletion not supported");
}
EntityType getEntityType(); EntityType getEntityType();
} }

108
common/data/src/main/java/org/thingsboard/server/common/data/BaseDataWithAdditionalInfo.java

@ -0,0 +1,108 @@
/**
* 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;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* Created by ashvayka on 19.02.18.
*/
@Slf4j
public abstract class BaseDataWithAdditionalInfo<I extends UUIDBased> extends BaseData<I> implements HasAdditionalInfo {
public static final ObjectMapper mapper = new ObjectMapper();
@NoXss
private transient JsonNode additionalInfo;
@JsonIgnore
private byte[] additionalInfoBytes;
public BaseDataWithAdditionalInfo() {
super();
}
public BaseDataWithAdditionalInfo(I id) {
super(id);
}
public BaseDataWithAdditionalInfo(BaseDataWithAdditionalInfo<I> baseData) {
super(baseData);
setAdditionalInfo(baseData.getAdditionalInfo());
}
@Override
public JsonNode getAdditionalInfo() {
return getJson(() -> additionalInfo, () -> additionalInfoBytes);
}
public void setAdditionalInfo(JsonNode addInfo) {
setJson(addInfo, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
BaseDataWithAdditionalInfo<?> that = (BaseDataWithAdditionalInfo<?>) o;
return Arrays.equals(additionalInfoBytes, that.additionalInfoBytes);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), additionalInfoBytes);
}
public static JsonNode getJson(Supplier<JsonNode> jsonData, Supplier<byte[]> binaryData) {
JsonNode json = jsonData.get();
if (json != null) {
return json;
} else {
byte[] data = binaryData.get();
if (data != null) {
try {
return mapper.readTree(new ByteArrayInputStream(data));
} catch (IOException e) {
log.warn("Can't deserialize json data: ", e);
return null;
}
} else {
return null;
}
}
}
public static void setJson(JsonNode json, Consumer<JsonNode> jsonConsumer, Consumer<byte[]> bytesConsumer) {
jsonConsumer.accept(json);
try {
bytesConsumer.accept(mapper.writeValueAsBytes(json));
} catch (JsonProcessingException e) {
log.warn("Can't serialize json data: ", e);
}
}
}

7
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -40,7 +40,7 @@ import java.util.Optional;
@ApiModel @ApiModel
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Slf4j @Slf4j
public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity<DeviceId> { public class Device extends BaseDataWithAdditionalInfo<DeviceId> implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity<DeviceId> {
private static final long serialVersionUID = 2807343040519543363L; private static final long serialVersionUID = 2807343040519543363L;
@ -201,11 +201,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
} }
} }
@Override
public String getSearchText() {
return getName();
}
@ApiModelProperty(position = 10, value = "JSON object with Ota Package Id.") @ApiModelProperty(position = 10, value = "JSON object with Ota Package Id.")
public OtaPackageId getFirmwareId() { public OtaPackageId getFirmwareId() {
return firmwareId; return firmwareId;

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

@ -20,6 +20,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
@ -39,7 +40,7 @@ import java.io.Serializable;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class NotificationRule extends BaseData<NotificationRuleId> implements HasTenantId, HasName, Serializable { public class NotificationRule extends BaseData<NotificationRuleId> implements HasTenantId, HasName, ExportableEntity<NotificationRuleId>, Serializable {
private TenantId tenantId; private TenantId tenantId;
@NotBlank @NotBlank
@ -61,6 +62,8 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
private NotificationRuleConfig additionalConfig; private NotificationRuleConfig additionalConfig;
private NotificationRuleId externalId;
public NotificationRule(NotificationRule other) { public NotificationRule(NotificationRule other) {
super(other); super(other);
this.tenantId = other.tenantId; this.tenantId = other.tenantId;
@ -71,6 +74,7 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
this.triggerConfig = other.triggerConfig; this.triggerConfig = other.triggerConfig;
this.recipientsConfig = other.recipientsConfig; this.recipientsConfig = other.recipientsConfig;
this.additionalConfig = other.additionalConfig; this.additionalConfig = other.additionalConfig;
this.externalId = other.externalId;
} }
@JsonIgnore @JsonIgnore

16
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.notification.targets;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
@ -31,7 +32,7 @@ import javax.validation.constraints.NotNull;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class NotificationTarget extends BaseData<NotificationTargetId> implements HasTenantId, HasName { public class NotificationTarget extends BaseData<NotificationTargetId> implements HasTenantId, HasName, ExportableEntity<NotificationTargetId> {
private TenantId tenantId; private TenantId tenantId;
@NotBlank @NotBlank
@ -42,4 +43,17 @@ public class NotificationTarget extends BaseData<NotificationTargetId> implement
@Valid @Valid
private NotificationTargetConfig configuration; private NotificationTargetConfig configuration;
private NotificationTargetId externalId;
public NotificationTarget() {
}
public NotificationTarget(NotificationTarget other) {
super(other);
this.tenantId = other.tenantId;
this.name = other.name;
this.configuration = other.configuration;
this.externalId = other.externalId;
}
} }

17
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.notification.template;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.NotificationTemplateId;
@ -32,7 +33,7 @@ import javax.validation.constraints.NotNull;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class NotificationTemplate extends BaseData<NotificationTemplateId> implements HasTenantId, HasName { public class NotificationTemplate extends BaseData<NotificationTemplateId> implements HasTenantId, HasName, ExportableEntity<NotificationTemplateId> {
private TenantId tenantId; private TenantId tenantId;
@NoXss @NoXss
@ -46,4 +47,18 @@ public class NotificationTemplate extends BaseData<NotificationTemplateId> imple
@NotNull @NotNull
private NotificationTemplateConfig configuration; private NotificationTemplateConfig configuration;
private NotificationTemplateId externalId;
public NotificationTemplate() {
}
public NotificationTemplate(NotificationTemplate other) {
super(other);
this.tenantId = other.tenantId;
this.name = other.name;
this.notificationType = other.notificationType;
this.configuration = other.configuration != null ? other.configuration.copy() : null;
this.externalId = other.externalId;
}
} }

9
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Data @Data
@ -29,4 +30,12 @@ public class NotificationTemplateConfig {
@NotEmpty @NotEmpty
private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> deliveryMethodsTemplates; private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> deliveryMethodsTemplates;
public NotificationTemplateConfig copy() {
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates = new HashMap<>(deliveryMethodsTemplates);
templates.replaceAll((deliveryMethod, template) -> template.copy());
NotificationTemplateConfig copy = new NotificationTemplateConfig();
copy.setDeliveryMethodsTemplates(templates);
return copy;
}
} }

8
common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java

@ -27,6 +27,9 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
@ -48,7 +51,10 @@ import java.lang.annotation.Target;
@Type(name = "DASHBOARD", value = Dashboard.class), @Type(name = "DASHBOARD", value = Dashboard.class),
@Type(name = "CUSTOMER", value = Customer.class), @Type(name = "CUSTOMER", value = Customer.class),
@Type(name = "ENTITY_VIEW", value = EntityView.class), @Type(name = "ENTITY_VIEW", value = EntityView.class),
@Type(name = "WIDGETS_BUNDLE", value = WidgetsBundle.class) @Type(name = "WIDGETS_BUNDLE", value = WidgetsBundle.class),
@Type(name = "NOTIFICATION_TEMPLATE", value = NotificationTemplate.class),
@Type(name = "NOTIFICATION_TARGET", value = NotificationTarget.class),
@Type(name = "NOTIFICATION_RULE", value = NotificationRule.class)
}) })
@JsonIgnoreProperties(value = {"tenantId", "createdTime"}, ignoreUnknown = true) @JsonIgnoreProperties(value = {"tenantId", "createdTime"}, ignoreUnknown = true)
public @interface JsonTbEntity { public @interface JsonTbEntity {

8
dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java

@ -109,6 +109,14 @@ public abstract class DaoUtil {
return ids; return ids;
} }
public static <I> I toEntityId(UUID uuid, Function<UUID, I> creator) {
if (uuid != null) {
return creator.apply(uuid);
} else {
return null;
}
}
public static <T> void processInBatches(Function<PageLink, PageData<T>> finder, int batchSize, Consumer<T> processor) { public static <T> void processInBatches(Function<PageLink, PageData<T>> finder, int batchSize, Consumer<T> processor) {
processBatches(finder, batchSize, batch -> batch.getData().forEach(processor)); processBatches(finder, batchSize, batch -> batch.getData().forEach(processor));
} }

6
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java

@ -273,6 +273,12 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetPr
return Optional.ofNullable(findAssetProfileById(tenantId, new AssetProfileId(entityId.getId()))); return Optional.ofNullable(findAssetProfileById(tenantId, new AssetProfileId(entityId.getId())));
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteAssetProfile(tenantId, (AssetProfileId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.ASSET_PROFILE; return EntityType.ASSET_PROFILE;

6
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -450,6 +450,12 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
return assetDao.countByTenantId(tenantId); return assetDao.countByTenantId(tenantId);
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteAsset(tenantId, (AssetId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.ASSET; return EntityType.ASSET;

6
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -197,6 +197,12 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
return Optional.ofNullable(findCustomerById(tenantId, new CustomerId(entityId.getId()))); return Optional.ofNullable(findCustomerById(tenantId, new CustomerId(entityId.getId())));
} }
@Transactional
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteCustomer(tenantId, (CustomerId) id);
}
@Override @Override
public long countByTenantId(TenantId tenantId) { public long countByTenantId(TenantId tenantId) {
return customerDao.countByTenantId(tenantId); return customerDao.countByTenantId(tenantId);

6
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -366,6 +366,12 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
return dashboardDao.countByTenantId(tenantId); return dashboardDao.countByTenantId(tenantId);
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteDashboard(tenantId, (DashboardId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.DASHBOARD; return EntityType.DASHBOARD;

6
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -328,6 +328,12 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
return Optional.ofNullable(findDeviceProfileById(tenantId, new DeviceProfileId(entityId.getId()))); return Optional.ofNullable(findDeviceProfileById(tenantId, new DeviceProfileId(entityId.getId())));
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteDeviceProfile(tenantId, (DeviceProfileId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.DEVICE_PROFILE; return EntityType.DEVICE_PROFILE;

6
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -638,6 +638,12 @@ public class DeviceServiceImpl extends AbstractCachedEntityService<DeviceCacheKe
return deviceDao.countByTenantId(tenantId); return deviceDao.countByTenantId(tenantId);
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteDevice(tenantId, (DeviceId) id);
}
private PaginatedRemover<TenantId, Device> tenantDevicesRemover = private PaginatedRemover<TenantId, Device> tenantDevicesRemover =
new PaginatedRemover<>() { new PaginatedRemover<>() {

7
dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java

@ -22,6 +22,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.dao.DaoUtil;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Id; import javax.persistence.Id;
@ -85,11 +86,7 @@ public abstract class BaseSqlEntity<D> implements BaseEntity<D> {
} }
protected static <I> I getEntityId(UUID uuid, Function<UUID, I> creator) { protected static <I> I getEntityId(UUID uuid, Function<UUID, I> creator) {
if (uuid != null) { return DaoUtil.toEntityId(uuid, creator);
return creator.apply(uuid);
} else {
return null;
}
} }
protected static TenantId getTenantId(UUID uuid) { protected static TenantId getTenantId(UUID uuid) {

17
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java

@ -32,7 +32,6 @@ import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.util.mapping.JsonBinaryType; import org.thingsboard.server.dao.util.mapping.JsonBinaryType;
import org.thingsboard.server.dao.util.mapping.JsonStringType; import org.thingsboard.server.dao.util.mapping.JsonStringType;
@ -47,7 +46,7 @@ import java.util.UUID;
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
}) })
@MappedSuperclass @MappedSuperclass
public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEntity<T> implements SearchTextEntity<T> { public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEntity<T> {
@Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY, columnDefinition = "uuid") @Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY, columnDefinition = "uuid")
private UUID tenantId; private UUID tenantId;
@ -64,9 +63,6 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
@Column(name = ModelConstants.DEVICE_LABEL_PROPERTY) @Column(name = ModelConstants.DEVICE_LABEL_PROPERTY)
private String label; private String label;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Type(type = "json") @Type(type = "json")
@Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo; private JsonNode additionalInfo;
@ -131,23 +127,12 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
this.type = deviceEntity.getType(); this.type = deviceEntity.getType();
this.name = deviceEntity.getName(); this.name = deviceEntity.getName();
this.label = deviceEntity.getLabel(); this.label = deviceEntity.getLabel();
this.searchText = deviceEntity.getSearchText();
this.additionalInfo = deviceEntity.getAdditionalInfo(); this.additionalInfo = deviceEntity.getAdditionalInfo();
this.firmwareId = deviceEntity.getFirmwareId(); this.firmwareId = deviceEntity.getFirmwareId();
this.softwareId = deviceEntity.getSoftwareId(); this.softwareId = deviceEntity.getSoftwareId();
this.externalId = deviceEntity.getExternalId(); this.externalId = deviceEntity.getExternalId();
} }
@Override
public String getSearchTextSource() {
return name;
}
@Override
public void setSearchText(String searchText) {
this.searchText = searchText;
}
protected Device toDevice() { protected Device toDevice() {
Device device = new Device(new DeviceId(getUuid())); Device device = new Device(new DeviceId(getUuid()));
device.setCreatedTime(createdTime); device.setCreatedTime(createdTime);

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

@ -72,6 +72,9 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
@Column(name = ModelConstants.NOTIFICATION_RULE_ADDITIONAL_CONFIG_PROPERTY) @Column(name = ModelConstants.NOTIFICATION_RULE_ADDITIONAL_CONFIG_PROPERTY)
private JsonNode additionalConfig; private JsonNode additionalConfig;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public NotificationRuleEntity() {} public NotificationRuleEntity() {}
public NotificationRuleEntity(NotificationRule notificationRule) { public NotificationRuleEntity(NotificationRule notificationRule) {
@ -85,6 +88,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
setTriggerConfig(toJson(notificationRule.getTriggerConfig())); setTriggerConfig(toJson(notificationRule.getTriggerConfig()));
setRecipientsConfig(toJson(notificationRule.getRecipientsConfig())); setRecipientsConfig(toJson(notificationRule.getRecipientsConfig()));
setAdditionalConfig(toJson(notificationRule.getAdditionalConfig())); setAdditionalConfig(toJson(notificationRule.getAdditionalConfig()));
setExternalId(getUuid(notificationRule.getExternalId()));
} }
public NotificationRuleEntity(NotificationRuleEntity other) { public NotificationRuleEntity(NotificationRuleEntity other) {
@ -98,6 +102,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
this.triggerConfig = other.triggerConfig; this.triggerConfig = other.triggerConfig;
this.recipientsConfig = other.recipientsConfig; this.recipientsConfig = other.recipientsConfig;
this.additionalConfig = other.additionalConfig; this.additionalConfig = other.additionalConfig;
this.externalId = other.externalId;
} }
@Override @Override
@ -113,6 +118,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
notificationRule.setTriggerConfig(fromJson(triggerConfig, NotificationRuleTriggerConfig.class)); notificationRule.setTriggerConfig(fromJson(triggerConfig, NotificationRuleTriggerConfig.class));
notificationRule.setRecipientsConfig(fromJson(recipientsConfig, NotificationRuleRecipientsConfig.class)); notificationRule.setRecipientsConfig(fromJson(recipientsConfig, NotificationRuleRecipientsConfig.class));
notificationRule.setAdditionalConfig(fromJson(additionalConfig, NotificationRuleConfig.class)); notificationRule.setAdditionalConfig(fromJson(additionalConfig, NotificationRuleConfig.class));
notificationRule.setExternalId(getEntityId(externalId, NotificationRuleId::new));
return notificationRule; return notificationRule;
} }

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java

@ -49,6 +49,9 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
@Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY, nullable = false)
private JsonNode configuration; private JsonNode configuration;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public NotificationTargetEntity() {} public NotificationTargetEntity() {}
public NotificationTargetEntity(NotificationTarget notificationTarget) { public NotificationTargetEntity(NotificationTarget notificationTarget) {
@ -57,6 +60,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
setTenantId(getTenantUuid(notificationTarget.getTenantId())); setTenantId(getTenantUuid(notificationTarget.getTenantId()));
setName(notificationTarget.getName()); setName(notificationTarget.getName());
setConfiguration(toJson(notificationTarget.getConfiguration())); setConfiguration(toJson(notificationTarget.getConfiguration()));
setExternalId(getUuid(notificationTarget.getExternalId()));
} }
@Override @Override
@ -67,6 +71,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
notificationTarget.setTenantId(getTenantId(tenantId)); notificationTarget.setTenantId(getTenantId(tenantId));
notificationTarget.setName(name); notificationTarget.setName(name);
notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class)); notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class));
notificationTarget.setExternalId(getEntityId(externalId, NotificationTargetId::new));
return notificationTarget; return notificationTarget;
} }

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java

@ -56,6 +56,9 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
@Column(name = ModelConstants.NOTIFICATION_TEMPLATE_CONFIGURATION_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_TEMPLATE_CONFIGURATION_PROPERTY, nullable = false)
private JsonNode configuration; private JsonNode configuration;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public NotificationTemplateEntity() {} public NotificationTemplateEntity() {}
public NotificationTemplateEntity(NotificationTemplate notificationTemplate) { public NotificationTemplateEntity(NotificationTemplate notificationTemplate) {
@ -65,6 +68,7 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
setName(notificationTemplate.getName()); setName(notificationTemplate.getName());
setNotificationType(notificationTemplate.getNotificationType()); setNotificationType(notificationTemplate.getNotificationType());
setConfiguration(toJson(notificationTemplate.getConfiguration())); setConfiguration(toJson(notificationTemplate.getConfiguration()));
setExternalId(getUuid(notificationTemplate.getExternalId()));
} }
@Override @Override
@ -76,6 +80,7 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
notificationTemplate.setName(name); notificationTemplate.setName(name);
notificationTemplate.setNotificationType(notificationType); notificationTemplate.setNotificationType(notificationType);
notificationTemplate.setConfiguration(fromJson(configuration, NotificationTemplateConfig.class)); notificationTemplate.setConfiguration(fromJson(configuration, NotificationTemplateConfig.class));
notificationTemplate.setExternalId(getEntityId(externalId, NotificationTemplateId::new));
return notificationTemplate; return notificationTemplate;
} }

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

@ -98,6 +98,11 @@ public class DefaultNotificationRuleService extends AbstractEntityService implem
return Optional.ofNullable(findNotificationRuleById(tenantId, new NotificationRuleId(entityId.getId()))); return Optional.ofNullable(findNotificationRuleById(tenantId, new NotificationRuleId(entityId.getId())));
} }
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteNotificationRuleById(tenantId, (NotificationRuleId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.NOTIFICATION_RULE; return EntityType.NOTIFICATION_RULE;

5
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java

@ -202,6 +202,11 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
return Optional.ofNullable(findNotificationTargetById(tenantId, new NotificationTargetId(entityId.getId()))); return Optional.ofNullable(findNotificationTargetById(tenantId, new NotificationTargetId(entityId.getId())));
} }
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteNotificationTargetById(tenantId, (NotificationTargetId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.NOTIFICATION_TARGET; return EntityType.NOTIFICATION_TARGET;

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

@ -94,6 +94,11 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im
return Optional.ofNullable(findNotificationTemplateById(tenantId, new NotificationTemplateId(entityId.getId()))); return Optional.ofNullable(findNotificationTemplateById(tenantId, new NotificationTemplateId(entityId.getId())));
} }
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteNotificationTemplateById(tenantId, (NotificationTemplateId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.NOTIFICATION_TEMPLATE; return EntityType.NOTIFICATION_TEMPLATE;

3
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java

@ -24,10 +24,11 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import java.util.List; import java.util.List;
public interface NotificationRuleDao extends Dao<NotificationRule> { public interface NotificationRuleDao extends Dao<NotificationRule>, ExportableEntityDao<NotificationRuleId, NotificationRule> {
PageData<NotificationRule> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); PageData<NotificationRule> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);

3
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java

@ -22,11 +22,12 @@ import org.thingsboard.server.common.data.notification.targets.NotificationTarge
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.TenantEntityDao; import org.thingsboard.server.dao.TenantEntityDao;
import java.util.List; import java.util.List;
public interface NotificationTargetDao extends Dao<NotificationTarget>, TenantEntityDao { public interface NotificationTargetDao extends Dao<NotificationTarget>, TenantEntityDao, ExportableEntityDao<NotificationTargetId, NotificationTarget> {
PageData<NotificationTarget> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); PageData<NotificationTarget> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);

4
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java

@ -15,16 +15,18 @@
*/ */
package org.thingsboard.server.dao.notification; package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import java.util.List; import java.util.List;
public interface NotificationTemplateDao extends Dao<NotificationTemplate> { public interface NotificationTemplateDao extends Dao<NotificationTemplate>, ExportableEntityDao<NotificationTemplateId, NotificationTemplate> {
PageData<NotificationTemplate> findByTenantIdAndNotificationTypesAndPageLink(TenantId tenantId, List<NotificationType> notificationTypes, PageLink pageLink); PageData<NotificationTemplate> findByTenantIdAndNotificationTypesAndPageLink(TenantId tenantId, List<NotificationType> notificationTypes, PageLink pageLink);

6
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -754,6 +754,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
return ruleChainDao.countByTenantId(tenantId); return ruleChainDao.countByTenantId(tenantId);
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteRuleChainById(tenantId, (RuleChainId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.RULE_CHAIN; return EntityType.RULE_CHAIN;

2
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -348,7 +348,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
@Override @Override
public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId id, long clearTs, JsonNode details) { public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId id, long clearTs, JsonNode details) {
return toAlarmApiResult(alarmRepository.clearAlarm(tenantId.getId(), id.getId(), clearTs, getDetailsAsString(details))); return toAlarmApiResult(alarmRepository.clearAlarm(tenantId.getId(), id.getId(), clearTs, details != null ? getDetailsAsString(details) : null));
} }
@Override @Override

42
dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java

@ -35,27 +35,30 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.customerId = :customerId " + "AND d.customerId = :customerId " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId,
@Param("customerId") UUID customerId, @Param("customerId") UUID customerId,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.deviceProfileId = :profileId " + "AND d.deviceProfileId = :profileId " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId,
@Param("profileId") UUID profileId, @Param("profileId") UUID profileId,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceInfoEntity d " + @Query("SELECT d FROM DeviceInfoEntity d " +
"WHERE d.tenantId = :tenantId " + "WHERE d.tenantId = :tenantId " +
"AND d.customerId = :customerId " + "AND d.customerId = :customerId " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId, Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId,
@Param("customerId") UUID customerId, @Param("customerId") UUID customerId,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId") @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId")
@ -63,14 +66,16 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantId(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantId(@Param("tenantId") UUID tenantId,
@Param("textSearch") String textSearch, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.type = :type " + "AND d.type = :type " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndType(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndType(@Param("tenantId") UUID tenantId,
@Param("type") String type, @Param("type") String type,
@Param("textSearch") String textSearch, @Param("textSearch") String textSearch,
@ -79,7 +84,8 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.deviceProfileId = :deviceProfileId " + "AND d.deviceProfileId = :deviceProfileId " +
"AND d.firmwareId = null " + "AND d.firmwareId = null " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndTypeAndFirmwareIdIsNull(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndTypeAndFirmwareIdIsNull(@Param("tenantId") UUID tenantId,
@Param("deviceProfileId") UUID deviceProfileId, @Param("deviceProfileId") UUID deviceProfileId,
@Param("textSearch") String textSearch, @Param("textSearch") String textSearch,
@ -88,7 +94,8 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.deviceProfileId = :deviceProfileId " + "AND d.deviceProfileId = :deviceProfileId " +
"AND d.softwareId = null " + "AND d.softwareId = null " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndTypeAndSoftwareIdIsNull(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndTypeAndSoftwareIdIsNull(@Param("tenantId") UUID tenantId,
@Param("deviceProfileId") UUID deviceProfileId, @Param("deviceProfileId") UUID deviceProfileId,
@Param("textSearch") String textSearch, @Param("textSearch") String textSearch,
@ -109,7 +116,8 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.customerId = :customerId " + "AND d.customerId = :customerId " +
"AND d.type = :type " + "AND d.type = :type " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId,
@Param("customerId") UUID customerId, @Param("customerId") UUID customerId,
@Param("type") String type, @Param("type") String type,
@ -123,7 +131,7 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
"AND ((:deviceType) IS NULL OR d.type = :deviceType) " + "AND ((:deviceType) IS NULL OR d.type = :deviceType) " +
"AND (:deviceProfileId IS NULL OR d.deviceProfileId = uuid(:deviceProfileId)) " + "AND (:deviceProfileId IS NULL OR d.deviceProfileId = uuid(:deviceProfileId)) " +
"AND ((:filterByActive) IS FALSE OR d.active = :deviceActive) " + "AND ((:filterByActive) IS FALSE OR d.active = :deviceActive) " +
"AND (LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + "OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.type) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + "OR LOWER(d.type) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.customerTitle) LIKE LOWER(CONCAT('%', :textSearch, '%')))") "OR LOWER(d.customerTitle) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
@ -155,21 +163,23 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
@Query("SELECT d FROM DeviceEntity d, RelationEntity re WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d, RelationEntity re WHERE d.tenantId = :tenantId " +
"AND d.id = re.toId AND re.toType = 'DEVICE' AND re.relationTypeGroup = 'EDGE' " + "AND d.id = re.toId AND re.toType = 'DEVICE' AND re.relationTypeGroup = 'EDGE' " +
"AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " + "AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndEdgeId(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndEdgeId(@Param("tenantId") UUID tenantId,
@Param("edgeId") UUID edgeId, @Param("edgeId") UUID edgeId,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceEntity d, RelationEntity re WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d, RelationEntity re WHERE d.tenantId = :tenantId " +
"AND d.id = re.toId AND re.toType = 'DEVICE' AND re.relationTypeGroup = 'EDGE' " + "AND d.id = re.toId AND re.toType = 'DEVICE' AND re.relationTypeGroup = 'EDGE' " +
"AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " + "AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " +
"AND d.type = :type " + "AND d.type = :type " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndEdgeIdAndType(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndEdgeIdAndType(@Param("tenantId") UUID tenantId,
@Param("edgeId") UUID edgeId, @Param("edgeId") UUID edgeId,
@Param("type") String type, @Param("type") String type,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
/** /**

4
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java

@ -40,7 +40,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.model.sql.DeviceEntity; import org.thingsboard.server.dao.model.sql.DeviceEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList; import java.util.ArrayList;
@ -56,7 +56,7 @@ import java.util.UUID;
@Component @Component
@SqlDao @SqlDao
@Slf4j @Slf4j
public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao { public class JpaDeviceDao extends JpaAbstractDao<DeviceEntity, Device> implements DeviceDao {
@Autowired @Autowired
private DeviceRepository deviceRepository; private DeviceRepository deviceRepository;

20
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java

@ -82,6 +82,26 @@ public class JpaNotificationRuleDao extends JpaAbstractDao<NotificationRuleEntit
notificationRuleRepository.deleteByTenantId(tenantId.getId()); notificationRuleRepository.deleteByTenantId(tenantId.getId());
} }
@Override
public NotificationRule findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(notificationRuleRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public NotificationRule findByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(notificationRuleRepository.findByTenantIdAndName(tenantId, name));
}
@Override
public PageData<NotificationRule> findByTenantId(UUID tenantId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRuleRepository.findByTenantId(tenantId, DaoUtil.toPageable(pageLink)));
}
@Override
public NotificationRuleId getExternalIdByInternal(NotificationRuleId internalId) {
return DaoUtil.toEntityId(notificationRuleRepository.getExternalIdByInternal(internalId.getId()), NotificationRuleId::new);
}
@Override @Override
protected Class<NotificationRuleEntity> getEntityClass() { protected Class<NotificationRuleEntity> getEntityClass() {
return NotificationRuleEntity.class; return NotificationRuleEntity.class;

20
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java

@ -76,6 +76,26 @@ public class JpaNotificationTargetDao extends JpaAbstractDao<NotificationTargetE
return notificationTargetRepository.countByTenantId(tenantId.getId()); return notificationTargetRepository.countByTenantId(tenantId.getId());
} }
@Override
public NotificationTarget findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(notificationTargetRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public NotificationTarget findByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(notificationTargetRepository.findByTenantIdAndName(tenantId, name));
}
@Override
public PageData<NotificationTarget> findByTenantId(UUID tenantId, PageLink pageLink) {
return DaoUtil.toPageData(notificationTargetRepository.findByTenantId(tenantId, DaoUtil.toPageable(pageLink)));
}
@Override
public NotificationTargetId getExternalIdByInternal(NotificationTargetId internalId) {
return DaoUtil.toEntityId(notificationTargetRepository.getExternalIdByInternal(internalId.getId()), NotificationTargetId::new);
}
@Override @Override
protected Class<NotificationTargetEntity> getEntityClass() { protected Class<NotificationTargetEntity> getEntityClass() {
return NotificationTargetEntity.class; return NotificationTargetEntity.class;

21
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java

@ -20,6 +20,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -57,6 +58,26 @@ public class JpaNotificationTemplateDao extends JpaAbstractDao<NotificationTempl
notificationTemplateRepository.deleteByTenantId(tenantId.getId()); notificationTemplateRepository.deleteByTenantId(tenantId.getId());
} }
@Override
public NotificationTemplate findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(notificationTemplateRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public NotificationTemplate findByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(notificationTemplateRepository.findByTenantIdAndName(tenantId, name));
}
@Override
public PageData<NotificationTemplate> findByTenantId(UUID tenantId, PageLink pageLink) {
return DaoUtil.toPageData(notificationTemplateRepository.findByTenantId(tenantId, DaoUtil.toPageable(pageLink)));
}
@Override
public NotificationTemplateId getExternalIdByInternal(NotificationTemplateId internalId) {
return DaoUtil.toEntityId(notificationTemplateRepository.getExternalIdByInternal(internalId.getId()), NotificationTemplateId::new);
}
@Override @Override
protected JpaRepository<NotificationTemplateEntity, UUID> getRepository() { protected JpaRepository<NotificationTemplateEntity, UUID> getRepository() {
return notificationTemplateRepository; return notificationTemplateRepository;

10
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java

@ -23,6 +23,7 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.NotificationRuleEntity; import org.thingsboard.server.dao.model.sql.NotificationRuleEntity;
import org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity; import org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity;
@ -30,7 +31,7 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
@Repository @Repository
public interface NotificationRuleRepository extends JpaRepository<NotificationRuleEntity, UUID> { public interface NotificationRuleRepository extends JpaRepository<NotificationRuleEntity, UUID>, ExportableEntityRepository<NotificationRuleEntity> {
String RULE_INFO_QUERY = "SELECT new org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity(r, t.name, t.configuration) " + String RULE_INFO_QUERY = "SELECT new org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity(r, t.name, t.configuration) " +
"FROM NotificationRuleEntity r INNER JOIN NotificationTemplateEntity t ON r.templateId = t.id"; "FROM NotificationRuleEntity r INNER JOIN NotificationTemplateEntity t ON r.templateId = t.id";
@ -59,4 +60,11 @@ public interface NotificationRuleRepository extends JpaRepository<NotificationRu
@Transactional @Transactional
void deleteByTenantId(UUID tenantId); void deleteByTenantId(UUID tenantId);
NotificationRuleEntity findByTenantIdAndName(UUID tenantId, String name);
Page<NotificationRuleEntity> findByTenantId(UUID tenantId, Pageable pageable);
@Query("SELECT externalId FROM NotificationRuleEntity WHERE id = :id")
UUID getExternalIdByInternal(@Param("id") UUID internalId);
} }

10
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java

@ -22,13 +22,14 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.NotificationTargetEntity; import org.thingsboard.server.dao.model.sql.NotificationTargetEntity;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@Repository @Repository
public interface NotificationTargetRepository extends JpaRepository<NotificationTargetEntity, UUID> { public interface NotificationTargetRepository extends JpaRepository<NotificationTargetEntity, UUID>, ExportableEntityRepository<NotificationTargetEntity> {
@Query("SELECT t FROM NotificationTargetEntity t WHERE t.tenantId = :tenantId " + @Query("SELECT t FROM NotificationTargetEntity t WHERE t.tenantId = :tenantId " +
"AND (:searchText = '' OR lower(t.name) LIKE lower(concat('%', :searchText, '%')))") "AND (:searchText = '' OR lower(t.name) LIKE lower(concat('%', :searchText, '%')))")
@ -52,4 +53,11 @@ public interface NotificationTargetRepository extends JpaRepository<Notification
long countByTenantId(UUID tenantId); long countByTenantId(UUID tenantId);
NotificationTargetEntity findByTenantIdAndName(UUID tenantId, String name);
Page<NotificationTargetEntity> findByTenantId(UUID tenantId, Pageable pageable);
@Query("SELECT externalId FROM NotificationTargetEntity WHERE id = :id")
UUID getExternalIdByInternal(@Param("id") UUID internalId);
} }

10
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java

@ -23,13 +23,14 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity; import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@Repository @Repository
public interface NotificationTemplateRepository extends JpaRepository<NotificationTemplateEntity, UUID> { public interface NotificationTemplateRepository extends JpaRepository<NotificationTemplateEntity, UUID>, ExportableEntityRepository<NotificationTemplateEntity> {
@Query("SELECT t FROM NotificationTemplateEntity t WHERE t.tenantId = :tenantId AND " + @Query("SELECT t FROM NotificationTemplateEntity t WHERE t.tenantId = :tenantId AND " +
"t.notificationType IN :notificationTypes " + "t.notificationType IN :notificationTypes " +
@ -43,4 +44,11 @@ public interface NotificationTemplateRepository extends JpaRepository<Notificati
@Transactional @Transactional
void deleteByTenantId(UUID tenantId); void deleteByTenantId(UUID tenantId);
NotificationTemplateEntity findByTenantIdAndName(UUID tenantId, String name);
Page<NotificationTemplateEntity> findByTenantId(UUID tenantId, Pageable pageable);
@Query("SELECT externalId FROM NotificationTemplateEntity WHERE id = :id")
UUID getExternalIdByInternal(@Param("id") UUID internalId);
} }

44
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.query;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@ -66,6 +67,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class DefaultEntityQueryRepository implements EntityQueryRepository { public class DefaultEntityQueryRepository implements EntityQueryRepository {
private static final Map<EntityType, String> entityTableMap = new HashMap<>(); private static final Map<EntityType, String> entityTableMap = new HashMap<>();
private static final Map<EntityType, String> entityNameColumns = new HashMap<>();
private static final String SELECT_PHONE = " CASE WHEN entity.entity_type = 'TENANT' THEN (select phone from tenant where id = entity_id)" + private static final String SELECT_PHONE = " CASE WHEN entity.entity_type = 'TENANT' THEN (select phone from tenant where id = entity_id)" +
" WHEN entity.entity_type = 'CUSTOMER' THEN (select phone from customer where id = entity_id) END as phone"; " WHEN entity.entity_type = 'CUSTOMER' THEN (select phone from customer where id = entity_id) END as phone";
private static final String SELECT_ZIP = " CASE WHEN entity.entity_type = 'TENANT' THEN (select zip from tenant where id = entity_id)" + private static final String SELECT_ZIP = " CASE WHEN entity.entity_type = 'TENANT' THEN (select zip from tenant where id = entity_id)" +
@ -242,6 +244,24 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
entityTableMap.put(EntityType.DEVICE_PROFILE, "device_profile"); entityTableMap.put(EntityType.DEVICE_PROFILE, "device_profile");
entityTableMap.put(EntityType.ASSET_PROFILE, "asset_profile"); entityTableMap.put(EntityType.ASSET_PROFILE, "asset_profile");
entityTableMap.put(EntityType.TENANT_PROFILE, "tenant_profile"); entityTableMap.put(EntityType.TENANT_PROFILE, "tenant_profile");
entityNameColumns.put(EntityType.DEVICE, "name");
entityNameColumns.put(EntityType.CUSTOMER, "title");
entityNameColumns.put(EntityType.DASHBOARD, "title");
entityNameColumns.put(EntityType.RULE_CHAIN, "name");
entityNameColumns.put(EntityType.RULE_NODE, "name");
entityNameColumns.put(EntityType.OTA_PACKAGE, "title");
entityNameColumns.put(EntityType.ASSET_PROFILE, "name");
entityNameColumns.put(EntityType.ASSET, "name");
entityNameColumns.put(EntityType.DEVICE_PROFILE, "name");
entityNameColumns.put(EntityType.USER, "email");
entityNameColumns.put(EntityType.TENANT_PROFILE, "name");
entityNameColumns.put(EntityType.TENANT, "title");
entityNameColumns.put(EntityType.WIDGETS_BUNDLE, "title");
entityNameColumns.put(EntityType.ENTITY_VIEW, "name");
entityNameColumns.put(EntityType.TB_RESOURCE, "search_text");
entityNameColumns.put(EntityType.EDGE, "name");
entityNameColumns.put(EntityType.QUEUE, "name");
} }
public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{
@ -807,33 +827,38 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
private String entityNameQuery(QueryContext ctx, EntityNameFilter filter) { private String entityNameQuery(QueryContext ctx, EntityNameFilter filter) {
ctx.addStringParameter("entity_filter_name_filter", filter.getEntityNameFilter()); ctx.addStringParameter("entity_filter_name_filter", filter.getEntityNameFilter());
String nameColumn = getNameColumn(filter.getEntityType());
if (filter.getEntityNameFilter().startsWith("%") || filter.getEntityNameFilter().endsWith("%")) { if (filter.getEntityNameFilter().startsWith("%") || filter.getEntityNameFilter().endsWith("%")) {
return "lower(e.search_text) like lower(:entity_filter_name_filter)"; return String.format("lower(e.%s) like lower(:entity_filter_name_filter)", nameColumn);
} }
return "lower(e.search_text) like lower(concat(:entity_filter_name_filter, '%%'))"; return String.format("lower(e.%s) like lower(concat(:entity_filter_name_filter, '%%'))", nameColumn);
} }
private String typeQuery(QueryContext ctx, EntityFilter filter) { private String typeQuery(QueryContext ctx, EntityFilter filter) {
List<String> types; List<String> types;
String name; String name;
String nameColumn;
switch (filter.getType()) { switch (filter.getType()) {
case ASSET_TYPE: case ASSET_TYPE:
types = ((AssetTypeFilter) filter).getAssetTypes(); types = ((AssetTypeFilter) filter).getAssetTypes();
name = ((AssetTypeFilter) filter).getAssetNameFilter(); name = ((AssetTypeFilter) filter).getAssetNameFilter();
nameColumn = getNameColumn(EntityType.ASSET);
break; break;
case DEVICE_TYPE: case DEVICE_TYPE:
types = ((DeviceTypeFilter) filter).getDeviceTypes(); types = ((DeviceTypeFilter) filter).getDeviceTypes();
name = ((DeviceTypeFilter) filter).getDeviceNameFilter(); name = ((DeviceTypeFilter) filter).getDeviceNameFilter();
nameColumn = getNameColumn(EntityType.DEVICE);
break; break;
case ENTITY_VIEW_TYPE: case ENTITY_VIEW_TYPE:
types = ((EntityViewTypeFilter) filter).getEntityViewTypes(); types = ((EntityViewTypeFilter) filter).getEntityViewTypes();
name = ((EntityViewTypeFilter) filter).getEntityViewNameFilter(); name = ((EntityViewTypeFilter) filter).getEntityViewNameFilter();
nameColumn = getNameColumn(EntityType.ENTITY_VIEW);
break; break;
case EDGE_TYPE: case EDGE_TYPE:
types = ((EdgeTypeFilter) filter).getEdgeTypes(); types = ((EdgeTypeFilter) filter).getEdgeTypes();
name = ((EdgeTypeFilter) filter).getEdgeNameFilter(); name = ((EdgeTypeFilter) filter).getEdgeNameFilter();
nameColumn = getNameColumn(EntityType.EDGE);
break; break;
default: default:
throw new RuntimeException("Not supported!"); throw new RuntimeException("Not supported!");
@ -843,14 +868,23 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
if (!StringUtils.isEmpty(name)) { if (!StringUtils.isEmpty(name)) {
ctx.addStringParameter("entity_filter_type_query_name", name); ctx.addStringParameter("entity_filter_type_query_name", name);
if (name.startsWith("%") || name.endsWith("%")) { if (name.startsWith("%") || name.endsWith("%")) {
return typesFilter + " and lower(e.search_text) like lower(:entity_filter_type_query_name)"; return typesFilter + " and lower(e." + nameColumn + ") like lower(:entity_filter_type_query_name)";
} }
return typesFilter + " and lower(e.search_text) like lower(concat(:entity_filter_type_query_name, '%%'))"; return typesFilter + " and lower(e." + nameColumn + ") like lower(concat(:entity_filter_type_query_name, '%%'))";
} else { } else {
return typesFilter; return typesFilter;
} }
} }
private String getNameColumn(EntityType entityType) {
String nameColumn = entityNameColumns.get(entityType);
if (nameColumn == null) {
log.error("Name column is not defined in the entityNameColumns map for entity type {}.", entityType);
throw new RuntimeException("Name column is not defined for entity type: " + entityType);
}
return nameColumn;
}
public static EntityType resolveEntityType(EntityFilter entityFilter) { public static EntityType resolveEntityType(EntityFilter entityFilter) {
switch (entityFilter.getType()) { switch (entityFilter.getType()) {
case SINGLE_ENTITY: case SINGLE_ENTITY:

5
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java

@ -160,6 +160,11 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
return Optional.ofNullable(findWidgetsBundleById(tenantId, new WidgetsBundleId(entityId.getId()))); return Optional.ofNullable(findWidgetsBundleById(tenantId, new WidgetsBundleId(entityId.getId())));
} }
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteWidgetsBundle(tenantId, (WidgetsBundleId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.WIDGETS_BUNDLE; return EntityType.WIDGETS_BUNDLE;

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

@ -340,7 +340,6 @@ CREATE TABLE IF NOT EXISTS device (
type varchar(255), type varchar(255),
name varchar(255), name varchar(255),
label varchar(255), label varchar(255),
search_text varchar(255),
tenant_id uuid, tenant_id uuid,
firmware_id uuid, firmware_id uuid,
software_id uuid, software_id uuid,
@ -797,7 +796,9 @@ CREATE TABLE IF NOT EXISTS notification_target (
tenant_id UUID NOT NULL, tenant_id UUID NOT NULL,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
configuration VARCHAR(10000) NOT NULL, configuration VARCHAR(10000) NOT NULL,
CONSTRAINT uq_notification_target_name UNIQUE (tenant_id, name) external_id UUID,
CONSTRAINT uq_notification_target_name UNIQUE (tenant_id, name),
CONSTRAINT uq_notification_target_external_id UNIQUE (tenant_id, external_id)
); );
CREATE TABLE IF NOT EXISTS notification_template ( CREATE TABLE IF NOT EXISTS notification_template (
@ -807,7 +808,9 @@ CREATE TABLE IF NOT EXISTS notification_template (
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
notification_type VARCHAR(50) NOT NULL, notification_type VARCHAR(50) NOT NULL,
configuration VARCHAR(10000000) NOT NULL, configuration VARCHAR(10000000) NOT NULL,
CONSTRAINT uq_notification_template_name UNIQUE (tenant_id, name) external_id UUID,
CONSTRAINT uq_notification_template_name UNIQUE (tenant_id, name),
CONSTRAINT uq_notification_template_external_id UNIQUE (tenant_id, external_id)
); );
CREATE TABLE IF NOT EXISTS notification_rule ( CREATE TABLE IF NOT EXISTS notification_rule (
@ -821,7 +824,9 @@ CREATE TABLE IF NOT EXISTS notification_rule (
trigger_config VARCHAR(1000) NOT NULL, trigger_config VARCHAR(1000) NOT NULL,
recipients_config VARCHAR(10000) NOT NULL, recipients_config VARCHAR(10000) NOT NULL,
additional_config VARCHAR(255), additional_config VARCHAR(255),
CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name) external_id UUID,
CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name),
CONSTRAINT uq_notification_rule_external_id UNIQUE (tenant_id, external_id)
); );
CREATE TABLE IF NOT EXISTS notification_request ( CREATE TABLE IF NOT EXISTS notification_request (

6
dao/src/main/resources/sql/schema-views-and-functions.sql

@ -226,7 +226,11 @@ BEGIN
END IF; END IF;
IF NOT(existing.cleared) THEN IF NOT(existing.cleared) THEN
cleared = TRUE; cleared = TRUE;
UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id; IF a_details IS NULL THEN
UPDATE alarm a SET cleared = true, clear_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
ELSE
UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
END IF; END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text; RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text;

30
dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java

@ -202,7 +202,34 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
@Test @Test
public void testClearAlarmProcedure() { public void testClearAlarmProcedure() {
UUID tenantId = UUID.randomUUID(); UUID tenantId = UUID.randomUUID();
; UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
long clearTs = System.currentTimeMillis();
var details = JacksonUtil.newObjectNode().put("test", 123);
AlarmApiCallResult result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs, details);
AlarmInfo afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isCleared());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
assertEquals(details, result.getAlarm().getDetails());
result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode());
assertNotNull(result);
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertTrue(result.isSuccessful());
assertFalse(result.isCleared());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
}
@Test
public void testClearAlarmWithoutDetailsProcedure() {
UUID tenantId = UUID.randomUUID();
UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b"); UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b"); UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM"); Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
@ -216,6 +243,7 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
assertEquals(afterSave, result.getAlarm()); assertEquals(afterSave, result.getAlarm());
assertEquals(clearTs, result.getAlarm().getClearTs()); assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared()); assertTrue(result.getAlarm().isCleared());
assertEquals(alarm.getDetails(), result.getAlarm().getDetails());
result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode()); result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode());
assertNotNull(result); assertNotNull(result);
assertNotNull(result.getAlarm()); assertNotNull(result.getAlarm());

7
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java

@ -520,4 +520,11 @@ public class TestRestClient {
.then() .then()
.statusCode(HTTP_OK); .statusCode(HTTP_OK);
} }
public void setDevicePublic(DeviceId deviceId) {
given().spec(requestSpec)
.post("/api/customer/public/device/{deviceId}", deviceId.getId())
.then()
.statusCode(HTTP_OK);
}
} }

24
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java

@ -73,7 +73,7 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
private static final int WIDTH = 1680; private static final int WIDTH = 1680;
private static final int HEIGHT = 1050; private static final int HEIGHT = 1050;
private static final String REMOTE_WEBDRIVER_HOST = "http://localhost:4444"; private static final String REMOTE_WEBDRIVER_HOST = "http://localhost:4444";
protected final PageLink pageLink = new PageLink(10); protected final PageLink pageLink = new PageLink(30);
private final ContainerTestSuite instance = ContainerTestSuite.getInstance(); private final ContainerTestSuite instance = ContainerTestSuite.getInstance();
private JavascriptExecutor js; private JavascriptExecutor js;
public static final long WAIT_TIMEOUT = TimeUnit.SECONDS.toMillis(10); public static final long WAIT_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
@ -253,6 +253,13 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
} }
} }
public void deleteCustomerByName(String customerName) {
Customer customer = getCustomerByName(customerName);
if (customer != null) {
testRestClient.deleteCustomer(customer.getId());
}
}
public void deleteDeviceById(DeviceId deviceId) { public void deleteDeviceById(DeviceId deviceId) {
if (deviceId != null) { if (deviceId != null) {
testRestClient.deleteDevice(deviceId); testRestClient.deleteDevice(deviceId);
@ -283,4 +290,19 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
testRestClient.deleteDevice(device.getId()); testRestClient.deleteDevice(device.getId());
} }
} }
public void deleteDeviceProfileByTitle(String deviceProfileTitle) {
DeviceProfile deviceProfile = getDeviceProfileByName(deviceProfileTitle);
if (deviceProfile != null) {
testRestClient.deleteDeviseProfile(deviceProfile.getId());
}
}
public void assertInvisibilityOfElement(WebElement element) {
try {
new WebDriverWait(driver, duration).until(ExpectedConditions.invisibilityOf(element));
} catch (WebDriverException e) {
fail("Element " + element.toString() + " stay visible");
}
}
} }

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CustomerPageElements.java

@ -58,7 +58,7 @@ public class CustomerPageElements extends OtherPageElementsHelper {
private static final String ASSIGNED_BTN = "//button[@type='submit']"; private static final String ASSIGNED_BTN = "//button[@type='submit']";
private static final String HIDE_HOME_DASHBOARD_TOOLBAR = "//mat-checkbox[@formcontrolname='homeDashboardHideToolbar']//label"; private static final String HIDE_HOME_DASHBOARD_TOOLBAR = "//mat-checkbox[@formcontrolname='homeDashboardHideToolbar']//label";
private static final String FILTER_BTN = "//tb-filters-edit"; private static final String FILTER_BTN = "//tb-filters-edit";
private static final String TIME_BTN = "//tb-timewindow"; private static final String TIME_BTN = "//tb-timewindow[not(@hidelabel)]";
private static final String CUSTOMER_ICON_HEADER = "//tb-breadcrumb//span[contains(text(),'Customer %s')]"; private static final String CUSTOMER_ICON_HEADER = "//tb-breadcrumb//span[contains(text(),'Customer %s')]";
private static final String CUSTOMER_USER_ICON_HEADER = "Users"; private static final String CUSTOMER_USER_ICON_HEADER = "Users";
private static final String CUSTOMER_ASSETS_ICON_HEADER = "Assets"; private static final String CUSTOMER_ASSETS_ICON_HEADER = "Assets";

74
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java

@ -33,26 +33,26 @@ public class DevicePageElements extends OtherPageElementsHelper {
private static final String CLOSE_DEVICE_DETAILS_VIEW = "//header//mat-icon[contains(text(),'close')]/parent::button"; private static final String CLOSE_DEVICE_DETAILS_VIEW = "//header//mat-icon[contains(text(),'close')]/parent::button";
private static final String SUBMIT_ASSIGN_TO_CUSTOMER_BTN = "//button[@type='submit']"; private static final String SUBMIT_ASSIGN_TO_CUSTOMER_BTN = "//button[@type='submit']";
private static final String ADD_DEVICE_BTN = "//mat-icon[text() = 'insert_drive_file']/parent::button"; private static final String ADD_DEVICE_BTN = "//mat-icon[text() = 'insert_drive_file']/parent::button";
private static final String CREATE_DEVICE_NAME_FIELD = "//tb-device-wizard//input[@formcontrolname='name']";
private static final String HEADER_NAME_VIEW = "//header//div[@class='tb-details-title']/span"; private static final String HEADER_NAME_VIEW = "//header//div[@class='tb-details-title']/span";
private static final String DESCRIPTION_FIELD_CREATE_VIEW = "//tb-device-wizard//textarea[@formcontrolname='description']";
private static final String ADD_DEVICE_VIEW = "//tb-device-wizard"; private static final String ADD_DEVICE_VIEW = "//tb-device-wizard";
private static final String DELETE_BTN_DETAILS_TAB = "//span[contains(text(),'Delete device')]/parent::button"; private static final String DELETE_BTN_DETAILS_TAB = "//span[contains(text(),'Delete device')]/parent::button";
private static final String CHECKBOX_GATEWAY_EDIT = "//mat-checkbox[@formcontrolname='gateway']//label"; private static final String CHECKBOX_GATEWAY_EDIT = "//mat-checkbox[@formcontrolname='gateway']//label";
private static final String CHECKBOX_GATEWAY_CREATE = "//tb-device-wizard//mat-checkbox[@formcontrolname='gateway']//label";
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT = "//mat-checkbox[@formcontrolname='overwriteActivityTime']//label"; private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT = "//mat-checkbox[@formcontrolname='overwriteActivityTime']//label";
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_CREATE = "//tb-device-wizard//mat-checkbox[@formcontrolname='overwriteActivityTime']//label";
private static final String CHECKBOX_GATEWAY_DETAILS = "//mat-checkbox[@formcontrolname='gateway']//input"; private static final String CHECKBOX_GATEWAY_DETAILS = "//mat-checkbox[@formcontrolname='gateway']//input";
private static final String CHECKBOX_GATEWAY_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-gateway')]//mat-icon[text() = 'check_box']"; private static final String CHECKBOX_GATEWAY_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-gateway')]//mat-icon[text() = 'check_box']";
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_DETAILS = "//mat-checkbox[@formcontrolname='overwriteActivityTime']//input"; private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_DETAILS = "//mat-checkbox[@formcontrolname='overwriteActivityTime']//input";
private static final String CLEAR_PROFILE_FIELD_BTN = "//button[@aria-label='Clear']"; private static final String CLEAR_PROFILE_FIELD_BTN = "//button[@aria-label='Clear']";
private static final String DEVICE_PROFILE_REDIRECTED_BTN = "//a[@aria-label='Open device profile']"; private static final String DEVICE_PROFILE_REDIRECTED_BTN = "//a[@aria-label='Open device profile']";
private static final String DEVICE_LABEL_FIELD_CREATE = "//tb-device-wizard//input[@formcontrolname='label']";
private static final String DEVICE_LABEL_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-label')]/span"; private static final String DEVICE_LABEL_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-label')]/span";
private static final String DEVICE_CUSTOMER_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-customerTitle')]/span"; private static final String DEVICE_CUSTOMER_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-customerTitle')]/span";
private static final String CUSTOMER_OPTION_BNT = "//div[text() = 'Customer']/ancestor::mat-step-header";
private static final String ASSIGN_ON_CUSTOMER_FIELD = "//input[@formcontrolname='entity']";
private static final String DEVICE_LABEL_EDIT = "//input[@formcontrolname='label']"; private static final String DEVICE_LABEL_EDIT = "//input[@formcontrolname='label']";
private static final String DEVICE_DEVICE_PROFILE_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-deviceProfileName')]/span";
protected static final String ASSIGN_BTN = ENTITY + "/ancestor::mat-row//mat-icon[contains(text(),'assignment_ind')]/ancestor::button";
protected static final String UNASSIGN_BTN = ENTITY + "/ancestor::mat-row//mat-icon[contains(text(),' assignment_return')]/ancestor::button";
protected static final String ASSIGN_BTN_DETAILS_TAB = "//span[contains(text(),'Assign to customer')]/parent::button";
protected static final String UNASSIGN_BTN_DETAILS_TAB = "//span[contains(text(),'Unassign from customer')]/parent::button";
protected static final String ASSIGNED_FIELD_DETAILS_TAB = "//mat-label[text() = 'Assigned to customer']/parent::label/parent::div/input";
protected static final String ASSIGN_MARKED_DEVICE_BTN = "//mat-icon[text() = 'assignment_ind']/parent::button";
public WebElement device(String deviceName) { public WebElement device(String deviceName) {
return waitUntilElementToBeClickable(String.format(DEVICE, deviceName)); return waitUntilElementToBeClickable(String.format(DEVICE, deviceName));
@ -90,18 +90,10 @@ public class DevicePageElements extends OtherPageElementsHelper {
return waitUntilElementToBeClickable(ADD_DEVICE_BTN); return waitUntilElementToBeClickable(ADD_DEVICE_BTN);
} }
public WebElement nameField() {
return waitUntilElementToBeClickable(CREATE_DEVICE_NAME_FIELD);
}
public WebElement headerNameView() { public WebElement headerNameView() {
return waitUntilVisibilityOfElementLocated(HEADER_NAME_VIEW); return waitUntilVisibilityOfElementLocated(HEADER_NAME_VIEW);
} }
public WebElement descriptionFieldCreateField() {
return waitUntilElementToBeClickable(DESCRIPTION_FIELD_CREATE_VIEW);
}
public WebElement addDeviceView() { public WebElement addDeviceView() {
return waitUntilPresenceOfElementLocated(ADD_DEVICE_VIEW); return waitUntilPresenceOfElementLocated(ADD_DEVICE_VIEW);
} }
@ -114,18 +106,10 @@ public class DevicePageElements extends OtherPageElementsHelper {
return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_EDIT); return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_EDIT);
} }
public WebElement checkboxGatewayCreate() {
return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_CREATE);
}
public WebElement checkboxOverwriteActivityTimeEdit() { public WebElement checkboxOverwriteActivityTimeEdit() {
return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT); return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT);
} }
public WebElement checkboxOverwriteActivityTimeCreate() {
return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_CREATE);
}
public WebElement checkboxGatewayDetailsTab() { public WebElement checkboxGatewayDetailsTab() {
return waitUntilPresenceOfElementLocated(CHECKBOX_GATEWAY_DETAILS); return waitUntilPresenceOfElementLocated(CHECKBOX_GATEWAY_DETAILS);
} }
@ -146,22 +130,10 @@ public class DevicePageElements extends OtherPageElementsHelper {
return waitUntilElementToBeClickable(DEVICE_PROFILE_REDIRECTED_BTN); return waitUntilElementToBeClickable(DEVICE_PROFILE_REDIRECTED_BTN);
} }
public WebElement deviceLabelFieldCreate() {
return waitUntilElementToBeClickable(DEVICE_LABEL_FIELD_CREATE);
}
public WebElement deviceLabelOnPage(String deviceName) { public WebElement deviceLabelOnPage(String deviceName) {
return waitUntilVisibilityOfElementLocated(String.format(DEVICE_LABEL_PAGE, deviceName)); return waitUntilVisibilityOfElementLocated(String.format(DEVICE_LABEL_PAGE, deviceName));
} }
public WebElement customerOptionBtn() {
return waitUntilElementToBeClickable(CUSTOMER_OPTION_BNT);
}
public WebElement assignOnCustomerField() {
return waitUntilElementToBeClickable(ASSIGN_ON_CUSTOMER_FIELD);
}
public WebElement deviceCustomerOnPage(String deviceName) { public WebElement deviceCustomerOnPage(String deviceName) {
return waitUntilVisibilityOfElementLocated(String.format(DEVICE_CUSTOMER_PAGE, deviceName)); return waitUntilVisibilityOfElementLocated(String.format(DEVICE_CUSTOMER_PAGE, deviceName));
} }
@ -173,4 +145,36 @@ public class DevicePageElements extends OtherPageElementsHelper {
public WebElement deviceLabelDetailsField() { public WebElement deviceLabelDetailsField() {
return waitUntilVisibilityOfElementLocated(DEVICE_LABEL_EDIT); return waitUntilVisibilityOfElementLocated(DEVICE_LABEL_EDIT);
} }
public WebElement deviceDeviceProfileOnPage(String deviceProfileTitle) {
return waitUntilVisibilityOfElementLocated(String.format(DEVICE_DEVICE_PROFILE_PAGE, deviceProfileTitle));
}
public WebElement assignBtn(String deviceName) {
return waitUntilElementToBeClickable(String.format(ASSIGN_BTN, deviceName));
}
public WebElement assignBtnVisible(String deviceName) {
return waitUntilVisibilityOfElementLocated(String.format(ASSIGN_BTN, deviceName));
}
public WebElement unassignBtn(String deviceName) {
return waitUntilElementToBeClickable(String.format(UNASSIGN_BTN, deviceName));
}
public WebElement assignBtnDetailsTab() {
return waitUntilElementToBeClickable(ASSIGN_BTN_DETAILS_TAB);
}
public WebElement unassignBtnDetailsTab() {
return waitUntilElementToBeClickable(UNASSIGN_BTN_DETAILS_TAB);
}
public WebElement assignFieldDetailsTab() {
return waitUntilVisibilityOfElementLocated(ASSIGNED_FIELD_DETAILS_TAB);
}
public WebElement assignMarkedDeviceBtn() {
return waitUntilVisibilityOfElementLocated(ASSIGN_MARKED_DEVICE_BTN);
}
} }

42
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java

@ -43,18 +43,6 @@ public class DevicePageHelper extends DevicePageElements {
addDeviceBtn().click(); addDeviceBtn().click();
} }
public void enterName(String deviceName) {
enterText(nameField(), deviceName);
}
public void enterDescription(String description) {
enterText(descriptionFieldCreateField(), description);
}
public void enterLabel(String label) {
enterText(deviceLabelFieldCreate(), label);
}
public void deleteDeviceByRightSideBtn(String deviceName) { public void deleteDeviceByRightSideBtn(String deviceName) {
deleteBtn(deviceName).click(); deleteBtn(deviceName).click();
warningPopUpYesBtn().click(); warningPopUpYesBtn().click();
@ -87,10 +75,30 @@ public class DevicePageHelper extends DevicePageElements {
entityFromDropdown(deviceProfileName).click(); entityFromDropdown(deviceProfileName).click();
} }
public void assignOnCustomer(String customerTitle) { public void unassignedDeviceByRightSideBtn(String deviceName) {
customerOptionBtn().click(); unassignBtn(deviceName).click();
assignOnCustomerField().click(); warningPopUpYesBtn().click();
entityFromList(customerTitle).click(); }
sleep(2); //waiting for the action to count
public void unassignedDeviceFromDetailsTab() {
unassignBtnDetailsTab().click();
warningPopUpYesBtn().click();
}
public void selectDevices(String... deviceNames) {
for (String deviceName : deviceNames) {
checkBox(deviceName).click();
}
}
public void assignSelectedDevices(String... deviceNames) {
selectDevices(deviceNames);
assignMarkedDeviceBtn().click();
}
public void deleteSelectedDevices(String... deviceNames) {
selectDevices(deviceNames);
deleteSelectedBtn().click();
warningPopUpYesBtn().click();
} }
} }

42
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabElements.java

@ -0,0 +1,42 @@
/**
* 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.msa.ui.tabs;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.thingsboard.server.msa.ui.base.AbstractBasePage;
public class AssignDeviceTabElements extends AbstractBasePage {
public AssignDeviceTabElements(WebDriver driver) {
super(driver);
}
private static final String ASSIGN_ON_CUSTOMER_FIELD = "//input[@formcontrolname='entity']";
private static final String CUSTOMER_FROM_DROPDOWN = "//div[@role='listbox']/mat-option//span[contains(text(),'%s')]";
private static final String ASSIGN_BTN = "//button[@type='submit']";
public WebElement assignOnCustomerField() {
return waitUntilElementToBeClickable(ASSIGN_ON_CUSTOMER_FIELD);
}
public WebElement customerFromDropDown(String entityName) {
return waitUntilVisibilityOfElementLocated(String.format(CUSTOMER_FROM_DROPDOWN, entityName));
}
public WebElement assignBtn() {
return waitUntilElementToBeClickable(ASSIGN_BTN);
}
}

30
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabHelper.java

@ -0,0 +1,30 @@
/**
* 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.msa.ui.tabs;
import org.openqa.selenium.WebDriver;
public class AssignDeviceTabHelper extends AssignDeviceTabElements {
public AssignDeviceTabHelper(WebDriver driver) {
super(driver);
}
public void assignOnCustomer(String customerTitle) {
assignOnCustomerField().click();
customerFromDropDown(customerTitle).click();
assignBtn().click();
}
}

97
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabElements.java

@ -0,0 +1,97 @@
/**
* 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.msa.ui.tabs;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.thingsboard.server.msa.ui.base.AbstractBasePage;
public class CreateDeviceTabElements extends AbstractBasePage {
public CreateDeviceTabElements(WebDriver driver) {
super(driver);
}
private static final String CREATE_DEVICE_NAME_FIELD = "//tb-device-wizard//input[@formcontrolname='name']";
private static final String CREATE_NEW_DEVICE_PROFILE_RADIO_BTN = "//span[text() = 'Create new device profile']/ancestor::mat-radio-button";
private static final String SELECT_EXISTING_DEVICE_PROFILE_RADIO_BTN = "//span[text() = 'Select existing device profile']/ancestor::mat-radio-button";
private static final String DEVICE_PROFILE_TITLE_FIELD = "//input[@formcontrolname='newDeviceProfileTitle']";
private static final String ADD_BTN = "//span[text() = 'Add']";
private static final String CLEAR_PROFILE_FIELD_BTN = "//button[@aria-label='Clear']";
private static final String ENTITY_FROM_DROPDOWN = "//div[@role = 'listbox']//span[text() = '%s']";
private static final String ASSIGN_ON_CUSTOMER_FIELD = "//input[@formcontrolname='entity']";
private static final String CUSTOMER_OPTION_BNT = "//div[text() = 'Customer']/ancestor::mat-step-header";
private static final String CUSTOMER_FROM_DROPDOWN = "//div[@role='listbox']/mat-option//span[contains(text(),'%s')]";
private static final String DEVICE_LABEL_FIELD = "//tb-device-wizard//input[@formcontrolname='label']";
private static final String CHECKBOX_GATEWAY = "//tb-device-wizard//mat-checkbox[@formcontrolname='gateway']//label";
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME = "//tb-device-wizard//mat-checkbox[@formcontrolname='overwriteActivityTime']//label";
private static final String DESCRIPTION_FIELD = "//tb-device-wizard//textarea[@formcontrolname='description']";
public WebElement nameField() {
return waitUntilElementToBeClickable(CREATE_DEVICE_NAME_FIELD);
}
public WebElement createNewDeviceProfileRadioBtn() {
return waitUntilElementToBeClickable(CREATE_NEW_DEVICE_PROFILE_RADIO_BTN);
}
public WebElement selectExistingDeviceProfileRadioBtn() {
return waitUntilElementToBeClickable(SELECT_EXISTING_DEVICE_PROFILE_RADIO_BTN);
}
public WebElement deviceProfileTitleField() {
return waitUntilElementToBeClickable(DEVICE_PROFILE_TITLE_FIELD);
}
public WebElement addBtn() {
return waitUntilElementToBeClickable(ADD_BTN);
}
public WebElement clearProfileFieldBtn() {
return waitUntilElementToBeClickable(CLEAR_PROFILE_FIELD_BTN);
}
public WebElement entityFromDropdown(String customerTitle) {
return waitUntilElementToBeClickable(String.format(ENTITY_FROM_DROPDOWN, customerTitle));
}
public WebElement assignOnCustomerField() {
return waitUntilElementToBeClickable(ASSIGN_ON_CUSTOMER_FIELD);
}
public WebElement customerOptionBtn() {
return waitUntilElementToBeClickable(CUSTOMER_OPTION_BNT);
}
public WebElement customerFromDropDown(String entityName) {
return waitUntilVisibilityOfElementLocated(String.format(CUSTOMER_FROM_DROPDOWN, entityName));
}
public WebElement deviceLabelField() {
return waitUntilElementToBeClickable(DEVICE_LABEL_FIELD);
}
public WebElement checkboxGateway() {
return waitUntilElementToBeClickable(CHECKBOX_GATEWAY);
}
public WebElement checkboxOverwriteActivityTime() {
return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME);
}
public WebElement descriptionField() {
return waitUntilElementToBeClickable(DESCRIPTION_FIELD);
}
}

58
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabHelper.java

@ -0,0 +1,58 @@
/**
* 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.msa.ui.tabs;
import org.openqa.selenium.WebDriver;
public class CreateDeviceTabHelper extends CreateDeviceTabElements {
public CreateDeviceTabHelper(WebDriver driver) {
super(driver);
}
public void enterName(String deviceName) {
enterText(nameField(), deviceName);
}
public void createNewDeviceProfile(String deviceProfileTitle) {
if (!createNewDeviceProfileRadioBtn().getAttribute("class").contains("checked")) {
createNewDeviceProfileRadioBtn().click();
}
deviceProfileTitleField().sendKeys(deviceProfileTitle);
}
public void changeDeviceProfile(String deviceProfileName) {
if (!selectExistingDeviceProfileRadioBtn().getAttribute("class").contains("checked")) {
selectExistingDeviceProfileRadioBtn().click();
}
clearProfileFieldBtn().click();
entityFromDropdown(deviceProfileName).click();
}
public void assignOnCustomer(String customerTitle) {
customerOptionBtn().click();
assignOnCustomerField().click();
customerFromDropDown(customerTitle).click();
sleep(2); //waiting for the action to count
}
public void enterLabel(String label) {
enterText(deviceLabelField(), label);
}
public void enterDescription(String description) {
enterText(descriptionField(), description);
}
}

8
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java

@ -22,24 +22,32 @@ import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.DevicePageHelper; import org.thingsboard.server.msa.ui.pages.DevicePageHelper;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper; import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewHelper; import org.thingsboard.server.msa.ui.pages.SideBarMenuViewHelper;
import org.thingsboard.server.msa.ui.tabs.CreateDeviceTabHelper;
@Epic("Device smoke tests") @Epic("Device smoke tests")
abstract public class AbstractDeviceTest extends AbstractDriverBaseTest { abstract public class AbstractDeviceTest extends AbstractDriverBaseTest {
protected SideBarMenuViewHelper sideBarMenuView; protected SideBarMenuViewHelper sideBarMenuView;
protected DevicePageHelper devicePage; protected DevicePageHelper devicePage;
protected CreateDeviceTabHelper createDeviceTab;
protected String deviceName; protected String deviceName;
protected String deviceProfileTitle;
@BeforeClass @BeforeClass
public void login() { public void login() {
new LoginPageHelper(driver).authorizationTenant(); new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver); sideBarMenuView = new SideBarMenuViewHelper(driver);
devicePage = new DevicePageHelper(driver); devicePage = new DevicePageHelper(driver);
createDeviceTab = new CreateDeviceTabHelper(driver);
} }
@AfterMethod @AfterMethod
public void delete() { public void delete() {
deleteDeviceByName(deviceName); deleteDeviceByName(deviceName);
deviceName = null; deviceName = null;
if (deviceProfileTitle != null) {
deleteDeviceProfileByTitle(deviceProfileTitle);
deviceProfileTitle = null;
}
} }
} }

193
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AssignToCustomerTest.java

@ -0,0 +1,193 @@
/**
* 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.msa.ui.tests.devicessmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import org.openqa.selenium.WebElement;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.msa.ui.pages.CustomerPageHelper;
import org.thingsboard.server.msa.ui.tabs.AssignDeviceTabHelper;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
@Feature("Assign to customer")
public class AssignToCustomerTest extends AbstractDeviceTest {
private AssignDeviceTabHelper assignDeviceTab;
private CustomerPageHelper customerPage;
private CustomerId customerId;
private Device device;
private Device device1;
private String customerName;
@BeforeClass
public void create() {
assignDeviceTab = new AssignDeviceTabHelper(driver);
customerPage = new CustomerPageHelper(driver);
Customer customer = testRestClient.postCustomer(EntityPrototypes.defaultCustomerPrototype(ENTITY_NAME + random()));
customerId = customer.getId();
customerName = customer.getName();
device1 = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype("Device " + random()));
}
@AfterClass
public void deleteCustomer() {
deleteCustomerById(customerId);
deleteCustomerByName("Public");
deleteDeviceByName(device1.getName());
}
@BeforeMethod
public void createDevice() {
device = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME));
deviceName = device.getName();
}
@Test(groups = "smoke")
@Description("Assign to customer by right side of device btn")
public void assignToCustomerByRightSideBtn() {
sideBarMenuView.goToDevicesPage();
devicePage.assignBtn(deviceName).click();
assignDeviceTab.assignOnCustomer(customerName);
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
.as("Customer added correctly").isEqualTo(customerName);
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
assertIsDisplayed(devicePage.device(deviceName));
}
@Test(groups = "smoke")
@Description("Assign to customer by 'Assign to customer' btn on details tab")
public void assignToCustomerFromDetailsTab() {
sideBarMenuView.goToDevicesPage();
devicePage.device(deviceName).click();
devicePage.assignBtnDetailsTab().click();
assignDeviceTab.assignOnCustomer(customerName);
String customerInAssignedField = devicePage.assignFieldDetailsTab().getAttribute("value");
devicePage.closeDeviceDetailsViewBtn().click();
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
.as("Customer added correctly").isEqualTo(customerName);
assertThat(customerInAssignedField)
.as("Customer in details tab added correctly").isEqualTo(customerName);
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
assertIsDisplayed(devicePage.device(deviceName));
}
@Test(groups = "smoke")
@Description("Assign marked device by btn on the top")
public void assignToCustomerMarkedDevice() {
sideBarMenuView.goToDevicesPage();
devicePage.assignSelectedDevices(deviceName);
assignDeviceTab.assignOnCustomer(customerName);
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
.as("Customer added correctly").isEqualTo(customerName);
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
assertIsDisplayed(devicePage.device(deviceName));
}
@Test(groups = "smoke")
@Description("Unassign from customer by right side of device btn")
public void unassignedFromCustomerByRightSideBtn() {
device.setCustomerId(customerId);
testRestClient.postDevice("", device);
sideBarMenuView.goToDevicesPage();
WebElement element = devicePage.deviceCustomerOnPage(deviceName);
devicePage.unassignedDeviceByRightSideBtn(deviceName);
assertInvisibilityOfElement(element);
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
devicePage.assertEntityIsNotPresent(deviceName);
}
@Test(groups = "smoke")
@Description("Unassign from customer by 'Unassign from customer' btn on details tab")
public void unassignedFromCustomerFromDetailsTab() {
device.setCustomerId(customerId);
testRestClient.postDevice("", device);
sideBarMenuView.goToDevicesPage();
WebElement customerInColumn = devicePage.deviceCustomerOnPage(deviceName);
devicePage.device(deviceName).click();
WebElement assignFieldDetailsTab = devicePage.assignFieldDetailsTab();
devicePage.unassignedDeviceFromDetailsTab();
assertInvisibilityOfElement(customerInColumn);
assertInvisibilityOfElement(assignFieldDetailsTab);
devicePage.closeDeviceDetailsViewBtn().click();
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
devicePage.assertEntityIsNotPresent(deviceName);
}
@Test(groups = "smoke")
@Description("Can't assign device on several customer")
public void assignToSeveralCustomer() {
device.setCustomerId(customerId);
testRestClient.postDevice("", device);
sideBarMenuView.goToDevicesPage();
assertIsDisable(devicePage.assignBtnVisible(deviceName));
}
@Test(groups = "smoke")
@Description("Can't assign public device")
public void assignPublicDevice() {
testRestClient.setDevicePublic(device.getId());
sideBarMenuView.goToDevicesPage();
assertIsDisable(devicePage.assignBtnVisible(deviceName));
}
@Test(groups = "smoke")
@Description("Assign several devices by btn on the top")
public void assignSeveralDevices() {
sideBarMenuView.goToDevicesPage();
devicePage.assignSelectedDevices(deviceName, device1.getName());
assignDeviceTab.assignOnCustomer(customerName);
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
.as("Customer added correctly").isEqualTo(customerName);
assertThat(devicePage.deviceCustomerOnPage(device1.getName()).getText())
.as("Customer added correctly").isEqualTo(customerName);
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
List.of(deviceName, device1.getName()).
forEach(d -> assertIsDisplayed(devicePage.device(d)));
}
}

97
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java

@ -19,6 +19,7 @@ import io.qameta.allure.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.msa.ui.pages.ProfilesPageElements;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes; import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -39,8 +40,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
devicePage.refreshBtn().click(); devicePage.refreshBtn().click();
assertIsDisplayed(devicePage.entity(deviceName)); assertIsDisplayed(devicePage.entity(deviceName));
@ -53,9 +54,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.enterDescription(deviceName); createDeviceTab.enterDescription(deviceName);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
devicePage.refreshBtn().click(); devicePage.refreshBtn().click();
devicePage.entity(deviceName).click(); devicePage.entity(deviceName).click();
devicePage.setHeaderName(); devicePage.setHeaderName();
@ -70,8 +71,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
public void createDeviceWithoutName() { public void createDeviceWithoutName() {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.nameField().click(); createDeviceTab.nameField().click();
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.addDeviceView()); assertIsDisplayed(devicePage.addDeviceView());
assertThat(devicePage.errorMessage().getText()).as("Text of warning message").isEqualTo(NAME_IS_REQUIRED_MESSAGE); assertThat(devicePage.errorMessage().getText()).as("Text of warning message").isEqualTo(NAME_IS_REQUIRED_MESSAGE);
@ -82,8 +83,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
public void createDeviceWithOnlySpace() { public void createDeviceWithOnlySpace() {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(" "); createDeviceTab.enterName(" ");
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.warningMessage()); assertIsDisplayed(devicePage.warningMessage());
assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(EMPTY_DEVICE_MESSAGE); assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(EMPTY_DEVICE_MESSAGE);
@ -98,8 +99,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.warningMessage()); assertIsDisplayed(devicePage.warningMessage());
assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(SAME_NAME_WARNING_DEVICE_MESSAGE); assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(SAME_NAME_WARNING_DEVICE_MESSAGE);
@ -113,8 +114,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.entity(deviceName)); assertIsDisplayed(devicePage.entity(deviceName));
} }
@ -126,9 +127,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.clearProfileFieldBtn().click(); createDeviceTab.clearProfileFieldBtn().click();
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.errorMessage()); assertIsDisplayed(devicePage.errorMessage());
assertThat(devicePage.errorMessage().getText()).as("Text of warning message").isEqualTo(DEVICE_PROFILE_IS_REQUIRED_MESSAGE); assertThat(devicePage.errorMessage().getText()).as("Text of warning message").isEqualTo(DEVICE_PROFILE_IS_REQUIRED_MESSAGE);
@ -142,9 +143,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.checkboxGatewayCreate().click(); createDeviceTab.checkboxGateway().click();
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.device(deviceName)); assertIsDisplayed(devicePage.device(deviceName));
assertIsDisplayed(devicePage.checkboxGatewayPage(deviceName)); assertIsDisplayed(devicePage.checkboxGatewayPage(deviceName));
@ -157,10 +158,10 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.checkboxGatewayCreate().click(); createDeviceTab.checkboxGateway().click();
devicePage.checkboxOverwriteActivityTimeCreate().click(); createDeviceTab.checkboxOverwriteActivityTime().click();
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
devicePage.device(deviceName).click(); devicePage.device(deviceName).click();
assertThat(devicePage.checkboxOverwriteActivityTimeDetails().getAttribute("class").contains("selected")) assertThat(devicePage.checkboxOverwriteActivityTimeDetails().getAttribute("class").contains("selected"))
@ -175,9 +176,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.enterLabel(deviceLabel); createDeviceTab.enterLabel(deviceLabel);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.deviceLabelOnPage(deviceName)); assertIsDisplayed(devicePage.deviceLabelOnPage(deviceName));
assertThat(devicePage.deviceLabelOnPage(deviceName).getText()).as("Label added correctly").isEqualTo(deviceLabel); assertThat(devicePage.deviceLabelOnPage(deviceName).getText()).as("Label added correctly").isEqualTo(deviceLabel);
@ -191,9 +192,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.assignOnCustomer(customer); createDeviceTab.assignOnCustomer(customer);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName)); assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText()) assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
@ -211,4 +212,42 @@ public class CreateDeviceTest extends AbstractDeviceTest {
assertThat(urlContains(urlPath)).as("Redirected URL contains " + urlPath).isTrue(); assertThat(urlContains(urlPath)).as("Redirected URL contains " + urlPath).isTrue();
} }
@Test(groups = "smoke")
@Description("Create new device profile from create device")
public void createNewDeviceProfile() {
ProfilesPageElements profilesPage = new ProfilesPageElements(driver);
deviceName = ENTITY_NAME + random();
deviceProfileTitle = ENTITY_NAME + random();
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
createDeviceTab.enterName(deviceName);
createDeviceTab.createNewDeviceProfile(deviceProfileTitle);
createDeviceTab.addBtn().click();
devicePage.refreshBtn().click();
String deviceProfileColumn = devicePage.deviceDeviceProfileOnPage(deviceName).getText();
sideBarMenuView.openDeviceProfiles();
assertThat(deviceProfileColumn).as("Profile changed correctly").isEqualTo(deviceProfileTitle);
assertIsDisplayed(profilesPage.entity(deviceProfileTitle));
}
@Test(groups = "smoke")
@Description("Add device with changed device profile (from default to another)")
public void createDeviceWithChangedProfile() {
deviceName = ENTITY_NAME + random();
deviceProfileTitle = ENTITY_NAME + random();
testRestClient.postDeviceProfile(EntityPrototypes.defaultDeviceProfile(deviceProfileTitle));
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
createDeviceTab.enterName(deviceName);
createDeviceTab.changeDeviceProfile(deviceProfileTitle);
createDeviceTab.addBtn().click();
devicePage.refreshBtn().click();
assertThat(devicePage.deviceDeviceProfileOnPage(deviceName).getText())
.as("Profile changed correctly").isEqualTo(deviceProfileTitle);
}
} }

14
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeleteSeveralDevicesTest.java

@ -23,6 +23,8 @@ import org.testng.annotations.Test;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes; import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME; import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
@ -51,11 +53,11 @@ public class DeleteSeveralDevicesTest extends AbstractDeviceTest {
"that appears at the top") "that appears at the top")
public void deleteSeveralDevicesByTopBtn() { public void deleteSeveralDevicesByTopBtn() {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.deleteSelected(2); devicePage.deleteSelectedDevices(deviceName1, deviceName2);
devicePage.refreshBtn().click(); devicePage.refreshBtn().click();
devicePage.assertEntityIsNotPresent(deviceName1); List.of(deviceName1, deviceName2)
devicePage.assertEntityIsNotPresent(deviceName2); .forEach(d -> devicePage.assertEntityIsNotPresent(d));
} }
@Test(groups = "smoke") @Test(groups = "smoke")
@ -76,9 +78,9 @@ public class DeleteSeveralDevicesTest extends AbstractDeviceTest {
"that appears at the top without refresh") "that appears at the top without refresh")
public void deleteSeveralWithoutRefresh() { public void deleteSeveralWithoutRefresh() {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.deleteSelected(2); devicePage.deleteSelectedDevices(deviceName1, deviceName2);
devicePage.assertEntityIsNotPresent(deviceName1); List.of(deviceName1, deviceName2)
devicePage.assertEntityIsNotPresent(deviceName2); .forEach(d -> devicePage.assertEntityIsNotPresent(d));
} }
} }

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesByNameAndTypeLoader.java

@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.util; package org.thingsboard.rule.engine.util;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -32,7 +33,7 @@ public class EntitiesByNameAndTypeLoader {
EntityType.USER); EntityType.USER);
public static EntityId findEntityId(TbContext ctx, EntityType entityType, String entityName) { public static EntityId findEntityId(TbContext ctx, EntityType entityType, String entityName) {
SearchTextBasedWithAdditionalInfo<? extends EntityId> targetEntity; BaseData<? extends EntityId> targetEntity;
switch (entityType) { switch (entityType) {
case DEVICE: case DEVICE:
targetEntity = ctx.getDeviceService().findDeviceByTenantIdAndName(ctx.getTenantId(), entityName); targetEntity = ctx.getDeviceService().findDeviceByTenantIdAndName(ctx.getTenantId(), entityName);

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

@ -422,6 +422,14 @@ export class EntityService {
pageLink.sortOrder.property = 'name'; pageLink.sortOrder.property = 'name';
entitiesObservable = this.notificationService.getNotificationTargets(pageLink, subType as NotificationType, config); entitiesObservable = this.notificationService.getNotificationTargets(pageLink, subType as NotificationType, config);
break; break;
case EntityType.NOTIFICATION_TEMPLATE:
pageLink.sortOrder.property = 'name';
entitiesObservable = this.notificationService.getNotificationTemplates(pageLink, subType as NotificationType, config);
break;
case EntityType.NOTIFICATION_RULE:
pageLink.sortOrder.property = 'name';
entitiesObservable = this.notificationService.getNotificationRules(pageLink, config);
break;
} }
return entitiesObservable; return entitiesObservable;
} }

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

@ -35,7 +35,7 @@ import {
SlackConversation SlackConversation
} from '@shared/models/notification.models'; } from '@shared/models/notification.models';
import { User } from '@shared/models/user.model'; import { User } from '@shared/models/user.model';
import { isDefinedAndNotNull, isNotEmptyStr } from '@core/utils'; import { isNotEmptyStr } from '@core/utils';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -169,7 +169,7 @@ export class NotificationService {
public getNotificationTemplates(pageLink: PageLink, notificationTypes?: NotificationType, public getNotificationTemplates(pageLink: PageLink, notificationTypes?: NotificationType,
config?: RequestConfig): Observable<PageData<NotificationTemplate>> { config?: RequestConfig): Observable<PageData<NotificationTemplate>> {
let url = `/api/notification/templates${pageLink.toQuery()}`; let url = `/api/notification/templates${pageLink.toQuery()}`;
if (isDefinedAndNotNull(notificationTypes)) { if (isNotEmptyStr(notificationTypes)) {
url += `&notificationTypes=${notificationTypes}`; url += `&notificationTypes=${notificationTypes}`;
} }
return this.http.get<PageData<NotificationTemplate>>(url, defaultHttpOptionsFromConfig(config)); return this.http.get<PageData<NotificationTemplate>>(url, defaultHttpOptionsFromConfig(config));

4
ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html

@ -66,10 +66,10 @@
<mat-checkbox *ngIf="entityTypeFormGroup.get('entityType').value === entityTypes.DEVICE" formControlName="saveCredentials"> <mat-checkbox *ngIf="entityTypeFormGroup.get('entityType').value === entityTypes.DEVICE" formControlName="saveCredentials">
{{ 'version-control.export-credentials' | translate }} {{ 'version-control.export-credentials' | translate }}
</mat-checkbox> </mat-checkbox>
<mat-checkbox formControlName="saveAttributes"> <mat-checkbox *ngIf="!entityTypesWithoutRelatedData.has(entityTypeFormGroup.get('entityType').value)" formControlName="saveAttributes">
{{ 'version-control.export-attributes' | translate }} {{ 'version-control.export-attributes' | translate }}
</mat-checkbox> </mat-checkbox>
<mat-checkbox formControlName="saveRelations"> <mat-checkbox *ngIf="!entityTypesWithoutRelatedData.has(entityTypeFormGroup.get('entityType').value)" formControlName="saveRelations">
{{ 'version-control.export-relations' | translate }} {{ 'version-control.export-relations' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>

2
ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.ts

@ -29,6 +29,7 @@ import {
} from '@angular/forms'; } from '@angular/forms';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { import {
entityTypesWithoutRelatedData,
EntityTypeVersionCreateConfig, EntityTypeVersionCreateConfig,
exportableEntityTypes, exportableEntityTypes,
SyncStrategy, SyncStrategy,
@ -73,6 +74,7 @@ export class EntityTypesVersionCreateComponent extends PageComponent implements
syncStrategyTranslations = syncStrategyTranslationMap; syncStrategyTranslations = syncStrategyTranslationMap;
entityTypes = EntityType; entityTypes = EntityType;
entityTypesWithoutRelatedData = entityTypesWithoutRelatedData;
loading = true; loading = true;

4
ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html

@ -66,10 +66,10 @@
<mat-checkbox *ngIf="entityTypeFormGroup.get('entityType').value === entityTypes.DEVICE" formControlName="loadCredentials"> <mat-checkbox *ngIf="entityTypeFormGroup.get('entityType').value === entityTypes.DEVICE" formControlName="loadCredentials">
{{ 'version-control.load-credentials' | translate }} {{ 'version-control.load-credentials' | translate }}
</mat-checkbox> </mat-checkbox>
<mat-checkbox formControlName="loadAttributes"> <mat-checkbox *ngIf="!entityTypesWithoutRelatedData.has(entityTypeFormGroup.get('entityType').value)" formControlName="loadAttributes">
{{ 'version-control.load-attributes' | translate }} {{ 'version-control.load-attributes' | translate }}
</mat-checkbox> </mat-checkbox>
<mat-checkbox formControlName="loadRelations"> <mat-checkbox *ngIf="!entityTypesWithoutRelatedData.has(entityTypeFormGroup.get('entityType').value)" formControlName="loadRelations">
{{ 'version-control.load-relations' | translate }} {{ 'version-control.load-relations' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>

13
ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts

@ -18,22 +18,26 @@ import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } fro
import { import {
AbstractControl, AbstractControl,
ControlValueAccessor, ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormArray, UntypedFormArray,
UntypedFormBuilder, UntypedFormBuilder,
UntypedFormControl, UntypedFormControl,
UntypedFormGroup, UntypedFormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator, Validator,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { EntityTypeVersionLoadConfig, exportableEntityTypes, VersionCreationResult } from '@shared/models/vc.models'; import {
entityTypesWithoutRelatedData,
EntityTypeVersionLoadConfig,
exportableEntityTypes
} from '@shared/models/vc.models';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
import { MatCheckbox, MatCheckboxChange } from '@angular/material/checkbox'; import { MatCheckbox } from '@angular/material/checkbox';
import { TbPopoverService } from '@shared/components/popover.service'; import { TbPopoverService } from '@shared/components/popover.service';
import { RemoveOtherEntitiesConfirmComponent } from '@home/components/vc/remove-other-entities-confirm.component'; import { RemoveOtherEntitiesConfirmComponent } from '@home/components/vc/remove-other-entities-confirm.component';
@ -66,6 +70,7 @@ export class EntityTypesVersionLoadComponent extends PageComponent implements On
public entityTypesVersionLoadFormGroup: UntypedFormGroup; public entityTypesVersionLoadFormGroup: UntypedFormGroup;
entityTypes = EntityType; entityTypes = EntityType;
entityTypesWithoutRelatedData = entityTypesWithoutRelatedData;
loading = true; loading = true;

26
ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts

@ -36,7 +36,13 @@ import { Observable, of } from 'rxjs';
import { Polyline } from './polyline'; import { Polyline } from './polyline';
import { Polygon } from './polygon'; import { Polygon } from './polygon';
import { Circle } from './circle'; import { Circle } from './circle';
import { createTooltip, entitiesParseName, isCutPolygon, isJSON } from '@home/components/widget/lib/maps/maps-utils'; import {
createTooltip,
entitiesParseName,
isCutPolygon,
isJSON,
isValidLatLng
} from '@home/components/widget/lib/maps/maps-utils';
import { checkLngLat, createLoadingDiv } from '@home/components/widget/lib/maps/common-maps-utils'; import { checkLngLat, createLoadingDiv } from '@home/components/widget/lib/maps/common-maps-utils';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { import {
@ -148,7 +154,8 @@ export default abstract class LeafletMap {
if (isDefinedAndNotNull(markerColor) && tinycolor(markerColor).isValid()) { if (isDefinedAndNotNull(markerColor) && tinycolor(markerColor).isValid()) {
const parsedColor = tinycolor(markerColor); const parsedColor = tinycolor(markerColor);
return L.divIcon({ return L.divIcon({
html: `<div style="background-color: ${parsedColor.setAlpha(0.4).toRgbString()};" class="marker-cluster tb-cluster-marker-element">` + html: `<div style="background-color: ${parsedColor.setAlpha(0.4).toRgbString()};" ` +
`class="marker-cluster tb-cluster-marker-element">` +
`<div style="background-color: ${parsedColor.setAlpha(0.9).toRgbString()};"><span>` + childCount + '</span></div></div>', `<div style="background-color: ${parsedColor.setAlpha(0.9).toRgbString()};"><span>` + childCount + '</span></div></div>',
iconSize: new L.Point(40, 40), iconSize: new L.Point(40, 40),
className: 'tb-cluster-marker-container' className: 'tb-cluster-marker-container'
@ -378,7 +385,8 @@ export default abstract class LeafletMap {
}); });
}, },
}); });
this.map.pm.Toolbar.changeControlOrder(['tbMarker', 'tbRectangle', 'tbPolygon', 'tbCircle', 'editMode', 'dragMode', 'tbCut', 'removalMode', 'rotateMode']); this.map.pm.Toolbar.changeControlOrder(['tbMarker', 'tbRectangle', 'tbPolygon', 'tbCircle',
'editMode', 'dragMode', 'tbCut', 'removalMode', 'rotateMode']);
} }
this.map.pm.setLang('en', this.translateService.instant('widgets.maps'), 'en'); this.map.pm.setLang('en', this.translateService.instant('widgets.maps'), 'en');
@ -643,19 +651,19 @@ export default abstract class LeafletMap {
} }
} }
extractPosition(data: FormattedData): {x: number, y: number} { extractPosition(data: FormattedData): {x: number; y: number} {
if (!data) { if (!data) {
return null; return null;
} }
const lat = data[this.options.latKeyName]; const lat = data[this.options.latKeyName];
const lng = data[this.options.lngKeyName]; const lng = data[this.options.lngKeyName];
if (!isDefinedAndNotNull(lat) || isString(lat) || isNaN(lat) || !isDefinedAndNotNull(lng) || isString(lng) || isNaN(lng)) { if (!isValidLatLng(lat, lng)) {
return null; return null;
} }
return {x: lat, y: lng}; return {x: lat, y: lng};
} }
positionToLatLng(position: {x: number, y: number}): L.LatLng { positionToLatLng(position: {x: number; y: number}): L.LatLng {
return L.latLng(position.x, position.y) as L.LatLng; return L.latLng(position.x, position.y) as L.LatLng;
} }
@ -936,7 +944,7 @@ export default abstract class LeafletMap {
return; return;
} }
this.saveLocation(data, this.convertToCustomFormat(e.target._latlng)).subscribe(); this.saveLocation(data, this.convertToCustomFormat(e.target._latlng)).subscribe();
} };
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetMarkersSettings>, private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetMarkersSettings>,
updateBounds = true, callback?, snappable = false): Marker { updateBounds = true, callback?, snappable = false): Marker {
@ -1141,7 +1149,7 @@ export default abstract class LeafletMap {
} }
} }
this.saveLocation(data, this.convertPolygonToCustomFormat(coordinates)).subscribe(() => {}); this.saveLocation(data, this.convertPolygonToCustomFormat(coordinates)).subscribe(() => {});
} };
createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetPolygonSettings>, createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetPolygonSettings>,
updateBounds = true, snappable = false) { updateBounds = true, snappable = false) {
@ -1218,7 +1226,7 @@ export default abstract class LeafletMap {
const center = e.layer.getLatLng(); const center = e.layer.getLatLng();
const radius = e.layer.getRadius(); const radius = e.layer.getRadius();
this.saveLocation(data, this.convertCircleToCustomFormat(center, radius)).subscribe(() => {}); this.saveLocation(data, this.convertCircleToCustomFormat(center, radius)).subscribe(() => {});
} };
updateCircle(circlesData: FormattedData[], updateBounds = true) { updateCircle(circlesData: FormattedData[], updateBounds = true) {
const toDelete = new Set(Array.from(this.circles.keys())); const toDelete = new Set(Array.from(this.circles.keys()));

15
ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts

@ -20,7 +20,7 @@ import {
ShowTooltipAction, WidgetToolipSettings ShowTooltipAction, WidgetToolipSettings
} from './map-models'; } from './map-models';
import { Datasource, FormattedData } from '@app/shared/models/widget.models'; import { Datasource, FormattedData } from '@app/shared/models/widget.models';
import { fillDataPattern, processDataPattern, safeExecute } from '@core/utils'; import { fillDataPattern, isDefinedAndNotNull, isString, processDataPattern, safeExecute } from '@core/utils';
import { parseWithTranslation } from '@home/components/widget/lib/maps/common-maps-utils'; import { parseWithTranslation } from '@home/components/widget/lib/maps/common-maps-utils';
export function createTooltip(target: L.Layer, export function createTooltip(target: L.Layer,
@ -110,3 +110,16 @@ export function entitiesParseName(entities: FormattedData[], labelSettings: labe
} }
return entities; return entities;
} }
export const isValidLatitude = (latitude: any): boolean =>
isDefinedAndNotNull(latitude) &&
!isString(latitude) &&
!isNaN(latitude) && isFinite(latitude) && Math.abs(latitude) <= 90;
export const isValidLongitude = (longitude: any): boolean =>
isDefinedAndNotNull(longitude) &&
!isString(longitude) &&
!isNaN(longitude) && isFinite(longitude) && Math.abs(longitude) <= 180;
export const isValidLatLng = (latitude: any, longitude: any): boolean =>
isValidLatitude(latitude) && isValidLongitude(longitude);

1
ui-ngx/src/app/shared/components/value-input.component.ts

@ -80,6 +80,7 @@ export class ValueInputComponent implements OnInit, ControlValueAccessor {
if (res) { if (res) {
this.modelValue = res; this.modelValue = res;
this.inputForm.control.patchValue({value: this.modelValue}); this.inputForm.control.patchValue({value: this.modelValue});
this.updateView();
} }
} }
); );

6
ui-ngx/src/app/shared/models/entity-type.models.ts

@ -368,6 +368,8 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
EntityType.NOTIFICATION_RULE, EntityType.NOTIFICATION_RULE,
{ {
type: 'entity.type-notification-rule', type: 'entity.type-notification-rule',
typePlural: 'entity.type-notification-rules',
list: 'entity.list-of-notification-rules',
noEntities: 'notification.no-rules-notification', noEntities: 'notification.no-rules-notification',
search: 'notification.search-rules', search: 'notification.search-rules',
selectedEntities: 'notification.selected-rules' selectedEntities: 'notification.selected-rules'
@ -377,6 +379,8 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
EntityType.NOTIFICATION_TARGET, EntityType.NOTIFICATION_TARGET,
{ {
type: 'entity.type-notification-target', type: 'entity.type-notification-target',
typePlural: 'entity.type-notification-targets',
list: 'entity.list-of-notification-targets',
noEntities: 'notification.no-recipients-notification', noEntities: 'notification.no-recipients-notification',
search: 'notification.search-recipients', search: 'notification.search-recipients',
selectedEntities: 'notification.selected-recipients' selectedEntities: 'notification.selected-recipients'
@ -386,6 +390,8 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
EntityType.NOTIFICATION_TEMPLATE, EntityType.NOTIFICATION_TEMPLATE,
{ {
type: 'entity.type-notification-template', type: 'entity.type-notification-template',
typePlural: 'entity.type-notification-templates',
list: 'entity.list-of-notification-templates',
noEntities: 'notification.no-notification-templates', noEntities: 'notification.no-notification-templates',
search: 'notification.search-templates', search: 'notification.search-templates',
selectedEntities: 'notification.selected-template' selectedEntities: 'notification.selected-template'

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

@ -17,7 +17,7 @@
import { NotificationId } from '@shared/models/id/notification-id'; import { NotificationId } from '@shared/models/id/notification-id';
import { NotificationRequestId } from '@shared/models/id/notification-request-id'; import { NotificationRequestId } from '@shared/models/id/notification-request-id';
import { UserId } from '@shared/models/id/user-id'; import { UserId } from '@shared/models/id/user-id';
import { BaseData } from '@shared/models/base-data'; import { BaseData, ExportableEntity } from '@shared/models/base-data';
import { TenantId } from '@shared/models/id/tenant-id'; import { TenantId } from '@shared/models/id/tenant-id';
import { NotificationTargetId } from '@shared/models/id/notification-target-id'; import { NotificationTargetId } from '@shared/models/id/notification-target-id';
import { NotificationTemplateId } from '@shared/models/id/notification-template-id'; import { NotificationTemplateId } from '@shared/models/id/notification-template-id';
@ -25,7 +25,6 @@ import { EntityId } from '@shared/models/id/entity-id';
import { NotificationRuleId } from '@shared/models/id/notification-rule-id'; import { NotificationRuleId } from '@shared/models/id/notification-rule-id';
import { AlarmSearchStatus, AlarmSeverity, AlarmStatus } from '@shared/models/alarm.models'; import { AlarmSearchStatus, AlarmSeverity, AlarmStatus } from '@shared/models/alarm.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { User } from '@shared/models/user.model';
import { ApiFeature, ApiUsageStateValue } from '@shared/models/api-usage.models'; import { ApiFeature, ApiUsageStateValue } from '@shared/models/api-usage.models';
export interface Notification { export interface Notification {
@ -107,7 +106,7 @@ export interface SlackConversation {
type: string; type: string;
} }
export interface NotificationRule extends Omit<BaseData<NotificationRuleId>, 'label'>{ export interface NotificationRule extends Omit<BaseData<NotificationRuleId>, 'label'>, ExportableEntity<NotificationRuleId> {
tenantId: TenantId; tenantId: TenantId;
enabled: boolean; enabled: boolean;
templateId: NotificationTemplateId; templateId: NotificationTemplateId;
@ -235,7 +234,7 @@ export interface NonConfirmedNotificationEscalation {
targets: Array<string>; targets: Array<string>;
} }
export interface NotificationTarget extends Omit<BaseData<NotificationTargetId>, 'label'>{ export interface NotificationTarget extends Omit<BaseData<NotificationTargetId>, 'label'>, ExportableEntity<NotificationTargetId> {
tenantId: TenantId; tenantId: TenantId;
configuration: NotificationTargetConfig; configuration: NotificationTargetConfig;
} }
@ -280,7 +279,7 @@ export const NotificationTargetTypeTranslationMap = new Map<NotificationTargetTy
[NotificationTargetType.SLACK, 'notification.delivery-method.slack'] [NotificationTargetType.SLACK, 'notification.delivery-method.slack']
]); ]);
export interface NotificationTemplate extends Omit<BaseData<NotificationTemplateId>, 'label'>{ export interface NotificationTemplate extends Omit<BaseData<NotificationTemplateId>, 'label'>, ExportableEntity<NotificationTemplateId> {
tenantId: TenantId; tenantId: TenantId;
notificationType: NotificationType; notificationType: NotificationType;
configuration: NotificationTemplateConfig; configuration: NotificationTemplateConfig;

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

@ -30,9 +30,18 @@ export const exportableEntityTypes: Array<EntityType> = [
EntityType.DEVICE_PROFILE, EntityType.DEVICE_PROFILE,
EntityType.ASSET_PROFILE, EntityType.ASSET_PROFILE,
EntityType.RULE_CHAIN, EntityType.RULE_CHAIN,
EntityType.WIDGETS_BUNDLE EntityType.WIDGETS_BUNDLE,
EntityType.NOTIFICATION_TEMPLATE,
EntityType.NOTIFICATION_TARGET,
EntityType.NOTIFICATION_RULE
]; ];
export const entityTypesWithoutRelatedData: Set<EntityType> = new Set([
EntityType.NOTIFICATION_TEMPLATE,
EntityType.NOTIFICATION_TARGET,
EntityType.NOTIFICATION_RULE
]);
export interface VersionCreateConfig { export interface VersionCreateConfig {
saveRelations: boolean; saveRelations: boolean;
saveAttributes: boolean; saveAttributes: boolean;

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

@ -2071,9 +2071,15 @@
"type-queue": "Queue", "type-queue": "Queue",
"type-notification": "Notification", "type-notification": "Notification",
"type-notification-rule": "Notification rule", "type-notification-rule": "Notification rule",
"type-notification-rules": "Notification rules",
"list-of-notification-rules": "{ count, plural, =1 {One notification rule} other {List of # notification rules} }",
"type-notification-target": "Notification recipient", "type-notification-target": "Notification recipient",
"type-notification-targets": "Notification recipients",
"list-of-notification-targets": "{ count, plural, =1 {One notification recipient} other {List of # notification recipients} }",
"type-notification-request": "Notification request", "type-notification-request": "Notification request",
"type-notification-template": "Notification template" "type-notification-template": "Notification template",
"type-notification-templates": "Notification templates",
"list-of-notification-templates": "{ count, plural, =1 {One notification template} other {List of # notification templates} }"
}, },
"entity-field": { "entity-field": {
"created-time": "Created time", "created-time": "Created time",

Loading…
Cancel
Save