diff --git a/application/src/main/data/upgrade/3.5.1/schema_update.sql b/application/src/main/data/upgrade/3.5.1/schema_update.sql index 09ce186811..3bc2c99168 100644 --- a/application/src/main/data/upgrade/3.5.1/schema_update.sql +++ b/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; + +-- 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 diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 3a6f20d55e..0235e30fdf 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -259,17 +259,16 @@ public class ThingsboardInstallService { case "3.5.1": log.info("Upgrading ThingsBoard from version 3.5.1 to 3.5.2 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.5.1"); - entityDatabaseSchemaService.createOrUpdateViewsAndFunctions(); - entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry); - log.info("Updating system data..."); - systemDataLoaderService.updateSystemWidgets(); - installScripts.loadSystemLwm2mResources(); + //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; - //TODO update CacheCleanupService on the next version upgrade default: 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!"); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 4f57f37ea7..d9988b9f5b 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -720,6 +720,14 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService if (isOldSchema(conn, 3005000)) { schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.5.1", SCHEMA_UPDATE_SQL); 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;"); } log.info("Schema updated."); diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java index 1e54d8d48f..278fc0adf5 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java @@ -84,7 +84,8 @@ public class DefaultCacheCleanupService implements CacheCleanupService { break; case "3.4.4": log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0"); - clearCacheByName("deviceProfiles"); + clearAll(); + break; default: //Do nothing, since cache cleanup is optional. } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java index cb25d74de4..c8f8f945f9 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java +++ b/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 SUPPORTED_ENTITY_TYPES = List.of( EntityType.CUSTOMER, EntityType.ASSET_PROFILE, EntityType.ASSET, EntityType.RULE_CHAIN, 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 ); diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java index 456eeb8b86..3358e5182f 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java +++ b/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.ExportableEntity; 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.HasId; -import org.thingsboard.server.common.data.id.RuleChainId; 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.PageLink; import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.ExportableEntityDao; -import org.thingsboard.server.dao.asset.AssetProfileService; -import org.thingsboard.server.dao.asset.AssetService; -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.dao.entity.EntityDaoService; +import org.thingsboard.server.dao.entity.EntityServiceRegistry; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.AccessControlService; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.function.BiConsumer; @Service @TbCoreComponent @@ -60,11 +45,10 @@ import java.util.function.BiConsumer; public class DefaultExportableEntitiesService implements ExportableEntitiesService { private final Map> daos = new HashMap<>(); - private final Map> removers = new HashMap<>(); + private final EntityServiceRegistry entityServiceRegistry; private final AccessControlService accessControlService; - @Override public , I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId) { EntityType entityType = externalId.getEntityType(); @@ -152,11 +136,11 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi @Override public void removeById(TenantId tenantId, I id) { EntityType entityType = id.getEntityType(); - BiConsumer entityRemover = removers.get(entityType); - if (entityRemover == null) { + EntityDaoService entityService = entityServiceRegistry.getServiceByEntityType(entityType); + if (entityService == null) { throw new IllegalArgumentException("Unsupported entity type " + entityType); } - entityRemover.accept(tenantId, id); + entityService.deleteEntity(tenantId, id); } private > ExportableEntityDao 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); - }); - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java index 891a6411a3..09b96d68fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java +++ b/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.service.sync.vc.data.EntitiesExportCtx; +import java.util.Collection; import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Stream; public abstract class BaseEntityExportService, D extends EntityExportData> extends DefaultEntityExportService { @@ -47,4 +51,11 @@ public abstract class BaseEntityExportService getExternalIdOrElseInternalByUuid(ctx, uuid)); } + protected Stream toExternalIds(Collection internalIds, Function entityIdCreator, + EntitiesExportCtx ctx) { + return internalIds.stream().map(entityIdCreator) + .map(entityId -> getExternalIdOrElseInternal(ctx, entityId)) + .map(EntityId::getId); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java new file mode 100644 index 0000000000..0906fb83ef --- /dev/null +++ b/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, D extends EntityExportData> extends BaseEntityExportService> { + + @Override + protected void setRelatedEntities(EntitiesExportCtx ctx, NotificationRule notificationRule, EntityExportData exportData) { + notificationRule.setTemplateId(getExternalIdOrElseInternal(ctx, notificationRule.getTemplateId())); + + NotificationRuleTriggerConfig ruleTriggerConfig = notificationRule.getTriggerConfig(); + switch (ruleTriggerConfig.getTriggerType()) { + case DEVICE_ACTIVITY: { + DeviceActivityNotificationRuleTriggerConfig triggerConfig = (DeviceActivityNotificationRuleTriggerConfig) ruleTriggerConfig; + Set devices = triggerConfig.getDevices(); + if (devices != null) { + triggerConfig.setDevices(toExternalIds(devices, DeviceId::new, ctx).collect(Collectors.toSet())); + } + + Set 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 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> 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 targets = recipientsConfig.getTargets(); + targets = toExternalIds(targets, NotificationTargetId::new, ctx).collect(Collectors.toList()); + recipientsConfig.setTargets(targets); + break; + } + } + } + + @Override + public Set getSupportedEntityTypes() { + return Set.of(EntityType.NOTIFICATION_RULE); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTargetExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTargetExportService.java new file mode 100644 index 0000000000..f0a5da3efc --- /dev/null +++ b/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> { + + @Override + protected void setRelatedEntities(EntitiesExportCtx ctx, NotificationTarget notificationTarget, EntityExportData 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 getSupportedEntityTypes() { + return Set.of(EntityType.NOTIFICATION_TARGET); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTemplateExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTemplateExportService.java new file mode 100644 index 0000000000..d28f352bae --- /dev/null +++ b/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> { + + @Override + protected void setRelatedEntities(EntitiesExportCtx ctx, NotificationTemplate notificationTemplate, EntityExportData exportData) { + + } + + @Override + public Set getSupportedEntityTypes() { + return Set.of(EntityType.NOTIFICATION_TEMPLATE); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java new file mode 100644 index 0000000000..a7d7670c8c --- /dev/null +++ b/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> { + + 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 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 devices = triggerConfig.getDevices(); + if (devices != null) { + triggerConfig.setDevices(devices.stream().map(DeviceId::new) + .map(idProvider::getInternalId).map(UUIDBased::getId) + .collect(Collectors.toSet())); + } + + Set 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 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> 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 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 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; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTargetImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTargetImportService.java new file mode 100644 index 0000000000..a950a1592e --- /dev/null +++ b/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> { + + 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 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 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; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTemplateImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTemplateImportService.java new file mode 100644 index 0000000000..b98c17a412 --- /dev/null +++ b/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> { + + 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 exportData, IdProvider idProvider) { + return notificationTemplate; + } + + @Override + protected NotificationTemplate saveOrUpdate(EntitiesImportCtx ctx, NotificationTemplate notificationTemplate, EntityExportData 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; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index 76ecd5cfa2..455b897fcd 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/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(); createDevice("Device 1", "default", "111"); - await().atMost(15, TimeUnit.SECONDS) + await().atMost(30, TimeUnit.SECONDS) .untilAsserted(() -> { assertThat(getMyNotifications(false, 100)).size().isEqualTo(1); }); @@ -494,9 +494,10 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { rule.setEnabled(true); saveNotificationRule(rule); + TimeUnit.SECONDS.sleep(2); // for rule update event to reach rules cache createDevice("Device 3", "default", "333"); - await().atMost(15, TimeUnit.SECONDS) + await().atMost(30, TimeUnit.SECONDS) .untilAsserted(() -> { assertThat(getMyNotifications(false, 100)).size().isEqualTo(2); }); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java index e1b8bec633..3094efb5be 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java +++ b/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()); } + default void deleteEntity(TenantId tenantId, EntityId id) { + throw new IllegalArgumentException(getEntityType().getNormalName() + " deletion not supported"); + } + EntityType getEntityType(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/BaseDataWithAdditionalInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/BaseDataWithAdditionalInfo.java new file mode 100644 index 0000000000..b287956e4a --- /dev/null +++ b/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 extends BaseData 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 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 jsonData, Supplier 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 jsonConsumer, Consumer bytesConsumer) { + jsonConsumer.accept(json); + try { + bytesConsumer.accept(mapper.writeValueAsBytes(json)); + } catch (JsonProcessingException e) { + log.warn("Can't serialize json data: ", e); + } + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index 27457bda34..45e4244bcb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -40,7 +40,7 @@ import java.util.Optional; @ApiModel @EqualsAndHashCode(callSuper = true) @Slf4j -public class Device extends SearchTextBasedWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity { +public class Device extends BaseDataWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity { private static final long serialVersionUID = 2807343040519543363L; @@ -201,11 +201,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen } } - @Override - public String getSearchText() { - return getName(); - } - @ApiModelProperty(position = 10, value = "JSON object with Ota Package Id.") public OtaPackageId getFirmwareId() { return firmwareId; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java index 607ca7f639..5ca8dff863 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java +++ b/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.NoArgsConstructor; 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.HasTenantId; import org.thingsboard.server.common.data.id.NotificationRuleId; @@ -39,7 +40,7 @@ import java.io.Serializable; @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) -public class NotificationRule extends BaseData implements HasTenantId, HasName, Serializable { +public class NotificationRule extends BaseData implements HasTenantId, HasName, ExportableEntity, Serializable { private TenantId tenantId; @NotBlank @@ -61,6 +62,8 @@ public class NotificationRule extends BaseData implements Ha private NotificationRuleConfig additionalConfig; + private NotificationRuleId externalId; + public NotificationRule(NotificationRule other) { super(other); this.tenantId = other.tenantId; @@ -71,6 +74,7 @@ public class NotificationRule extends BaseData implements Ha this.triggerConfig = other.triggerConfig; this.recipientsConfig = other.recipientsConfig; this.additionalConfig = other.additionalConfig; + this.externalId = other.externalId; } @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java index 9a2f9a5306..df488d3b1e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java +++ b/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.EqualsAndHashCode; 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.HasTenantId; import org.thingsboard.server.common.data.id.NotificationTargetId; @@ -31,7 +32,7 @@ import javax.validation.constraints.NotNull; @Data @EqualsAndHashCode(callSuper = true) -public class NotificationTarget extends BaseData implements HasTenantId, HasName { +public class NotificationTarget extends BaseData implements HasTenantId, HasName, ExportableEntity { private TenantId tenantId; @NotBlank @@ -42,4 +43,17 @@ public class NotificationTarget extends BaseData implement @Valid 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; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java index 71da2f862b..48bc5dd409 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java +++ b/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.EqualsAndHashCode; 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.HasTenantId; import org.thingsboard.server.common.data.id.NotificationTemplateId; @@ -32,7 +33,7 @@ import javax.validation.constraints.NotNull; @Data @EqualsAndHashCode(callSuper = true) -public class NotificationTemplate extends BaseData implements HasTenantId, HasName { +public class NotificationTemplate extends BaseData implements HasTenantId, HasName, ExportableEntity { private TenantId tenantId; @NoXss @@ -46,4 +47,18 @@ public class NotificationTemplate extends BaseData imple @NotNull 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; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java index 1dcb4aec0c..91b10ed2be 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java +++ b/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.constraints.NotEmpty; +import java.util.HashMap; import java.util.Map; @Data @@ -29,4 +30,12 @@ public class NotificationTemplateConfig { @NotEmpty private Map deliveryMethodsTemplates; + public NotificationTemplateConfig copy() { + Map templates = new HashMap<>(deliveryMethodsTemplates); + templates.replaceAll((deliveryMethod, template) -> template.copy()); + NotificationTemplateConfig copy = new NotificationTemplateConfig(); + copy.setDeliveryMethodsTemplates(templates); + return copy; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java index dcb5c29e53..23a98b395b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java +++ b/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.asset.Asset; 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.widget.WidgetsBundle; @@ -48,7 +51,10 @@ import java.lang.annotation.Target; @Type(name = "DASHBOARD", value = Dashboard.class), @Type(name = "CUSTOMER", value = Customer.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) public @interface JsonTbEntity { diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java index 5a328daadb..63b13bca66 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java @@ -109,6 +109,14 @@ public abstract class DaoUtil { return ids; } + public static I toEntityId(UUID uuid, Function creator) { + if (uuid != null) { + return creator.apply(uuid); + } else { + return null; + } + } + public static void processInBatches(Function> finder, int batchSize, Consumer processor) { processBatches(finder, batchSize, batch -> batch.getData().forEach(processor)); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java index c464d06857..eb297e0049 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java @@ -273,6 +273,12 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService tenantDevicesRemover = new PaginatedRemover<>() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java index 916c459a6b..de3b81ee74 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java +++ b/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.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; +import org.thingsboard.server.dao.DaoUtil; import javax.persistence.Column; import javax.persistence.Id; @@ -85,11 +86,7 @@ public abstract class BaseSqlEntity implements BaseEntity { } protected static I getEntityId(UUID uuid, Function creator) { - if (uuid != null) { - return creator.apply(uuid); - } else { - return null; - } + return DaoUtil.toEntityId(uuid, creator); } protected static TenantId getTenantId(UUID uuid) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java index 403cb04aef..31ed02914b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java +++ b/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.dao.model.BaseSqlEntity; 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.JsonStringType; @@ -47,7 +46,7 @@ import java.util.UUID; @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) }) @MappedSuperclass -public abstract class AbstractDeviceEntity extends BaseSqlEntity implements SearchTextEntity { +public abstract class AbstractDeviceEntity extends BaseSqlEntity { @Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY, columnDefinition = "uuid") private UUID tenantId; @@ -64,9 +63,6 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti @Column(name = ModelConstants.DEVICE_LABEL_PROPERTY) private String label; - @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) - private String searchText; - @Type(type = "json") @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; @@ -131,23 +127,12 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti this.type = deviceEntity.getType(); this.name = deviceEntity.getName(); this.label = deviceEntity.getLabel(); - this.searchText = deviceEntity.getSearchText(); this.additionalInfo = deviceEntity.getAdditionalInfo(); this.firmwareId = deviceEntity.getFirmwareId(); this.softwareId = deviceEntity.getSoftwareId(); this.externalId = deviceEntity.getExternalId(); } - @Override - public String getSearchTextSource() { - return name; - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - protected Device toDevice() { Device device = new Device(new DeviceId(getUuid())); device.setCreatedTime(createdTime); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java index c3bc8cce0a..15283c9fc1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java @@ -72,6 +72,9 @@ public class NotificationRuleEntity extends BaseSqlEntity { @Column(name = ModelConstants.NOTIFICATION_RULE_ADDITIONAL_CONFIG_PROPERTY) private JsonNode additionalConfig; + @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) + private UUID externalId; + public NotificationRuleEntity() {} public NotificationRuleEntity(NotificationRule notificationRule) { @@ -85,6 +88,7 @@ public class NotificationRuleEntity extends BaseSqlEntity { setTriggerConfig(toJson(notificationRule.getTriggerConfig())); setRecipientsConfig(toJson(notificationRule.getRecipientsConfig())); setAdditionalConfig(toJson(notificationRule.getAdditionalConfig())); + setExternalId(getUuid(notificationRule.getExternalId())); } public NotificationRuleEntity(NotificationRuleEntity other) { @@ -98,6 +102,7 @@ public class NotificationRuleEntity extends BaseSqlEntity { this.triggerConfig = other.triggerConfig; this.recipientsConfig = other.recipientsConfig; this.additionalConfig = other.additionalConfig; + this.externalId = other.externalId; } @Override @@ -113,6 +118,7 @@ public class NotificationRuleEntity extends BaseSqlEntity { notificationRule.setTriggerConfig(fromJson(triggerConfig, NotificationRuleTriggerConfig.class)); notificationRule.setRecipientsConfig(fromJson(recipientsConfig, NotificationRuleRecipientsConfig.class)); notificationRule.setAdditionalConfig(fromJson(additionalConfig, NotificationRuleConfig.class)); + notificationRule.setExternalId(getEntityId(externalId, NotificationRuleId::new)); return notificationRule; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java index a4748ae0c2..62dbfc20fd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java @@ -49,6 +49,9 @@ public class NotificationTargetEntity extends BaseSqlEntity @Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY, nullable = false) private JsonNode configuration; + @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) + private UUID externalId; + public NotificationTargetEntity() {} public NotificationTargetEntity(NotificationTarget notificationTarget) { @@ -57,6 +60,7 @@ public class NotificationTargetEntity extends BaseSqlEntity setTenantId(getTenantUuid(notificationTarget.getTenantId())); setName(notificationTarget.getName()); setConfiguration(toJson(notificationTarget.getConfiguration())); + setExternalId(getUuid(notificationTarget.getExternalId())); } @Override @@ -67,6 +71,7 @@ public class NotificationTargetEntity extends BaseSqlEntity notificationTarget.setTenantId(getTenantId(tenantId)); notificationTarget.setName(name); notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class)); + notificationTarget.setExternalId(getEntityId(externalId, NotificationTargetId::new)); return notificationTarget; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java index 09b0ee7ee9..157217afe2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java @@ -56,6 +56,9 @@ public class NotificationTemplateEntity extends BaseSqlEntity { +public interface NotificationRuleDao extends Dao, ExportableEntityDao { PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java index 2edbfc580a..912e873bcc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java +++ b/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.PageLink; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.ExportableEntityDao; import org.thingsboard.server.dao.TenantEntityDao; import java.util.List; -public interface NotificationTargetDao extends Dao, TenantEntityDao { +public interface NotificationTargetDao extends Dao, TenantEntityDao, ExportableEntityDao { PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java index e88f3351cf..a2a896ffa4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java @@ -15,16 +15,18 @@ */ 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.notification.NotificationType; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.ExportableEntityDao; import java.util.List; -public interface NotificationTemplateDao extends Dao { +public interface NotificationTemplateDao extends Dao, ExportableEntityDao { PageData findByTenantIdAndNotificationTypesAndPageLink(TenantId tenantId, List notificationTypes, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 76947c5043..ba4afa7bd0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/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); } + @Override + @Transactional + public void deleteEntity(TenantId tenantId, EntityId id) { + deleteRuleChainById(tenantId, (RuleChainId) id); + } + @Override public EntityType getEntityType() { return EntityType.RULE_CHAIN; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index d9e79dd594..8dfdaf8a3e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -348,7 +348,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A @Override 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 diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 89ed713975..56f6a7c647 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -35,27 +35,30 @@ public interface DeviceRepository extends JpaRepository, Exp @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "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 findByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId, @Param("customerId") UUID customerId, - @Param("searchText") String searchText, + @Param("textSearch") String textSearch, Pageable pageable); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "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 findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId, @Param("profileId") UUID profileId, - @Param("searchText") String searchText, + @Param("textSearch") String textSearch, Pageable pageable); @Query("SELECT d FROM DeviceInfoEntity d " + "WHERE d.tenantId = :tenantId " + "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 findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId, @Param("customerId") UUID customerId, - @Param("searchText") String searchText, + @Param("textSearch") String textSearch, Pageable pageable); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId") @@ -63,14 +66,16 @@ public interface DeviceRepository extends JpaRepository, Exp Pageable pageable); @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 findByTenantId(@Param("tenantId") UUID tenantId, @Param("textSearch") String textSearch, Pageable pageable); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "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 findByTenantIdAndType(@Param("tenantId") UUID tenantId, @Param("type") String type, @Param("textSearch") String textSearch, @@ -79,7 +84,8 @@ public interface DeviceRepository extends JpaRepository, Exp @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.deviceProfileId = :deviceProfileId " + "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 findByTenantIdAndTypeAndFirmwareIdIsNull(@Param("tenantId") UUID tenantId, @Param("deviceProfileId") UUID deviceProfileId, @Param("textSearch") String textSearch, @@ -88,7 +94,8 @@ public interface DeviceRepository extends JpaRepository, Exp @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.deviceProfileId = :deviceProfileId " + "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 findByTenantIdAndTypeAndSoftwareIdIsNull(@Param("tenantId") UUID tenantId, @Param("deviceProfileId") UUID deviceProfileId, @Param("textSearch") String textSearch, @@ -109,7 +116,8 @@ public interface DeviceRepository extends JpaRepository, Exp @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.customerId = :customerId " + "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 findByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId, @Param("customerId") UUID customerId, @Param("type") String type, @@ -123,7 +131,7 @@ public interface DeviceRepository extends JpaRepository, Exp "AND ((:deviceType) IS NULL OR d.type = :deviceType) " + "AND (:deviceProfileId IS NULL OR d.deviceProfileId = uuid(:deviceProfileId)) " + "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.type) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + "OR LOWER(d.customerTitle) LIKE LOWER(CONCAT('%', :textSearch, '%')))") @@ -155,21 +163,23 @@ public interface DeviceRepository extends JpaRepository, Exp @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 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 findByTenantIdAndEdgeId(@Param("tenantId") UUID tenantId, @Param("edgeId") UUID edgeId, - @Param("searchText") String searchText, + @Param("textSearch") String textSearch, Pageable pageable); @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 re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " + "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 findByTenantIdAndEdgeIdAndType(@Param("tenantId") UUID tenantId, @Param("edgeId") UUID edgeId, @Param("type") String type, - @Param("searchText") String searchText, + @Param("textSearch") String textSearch, Pageable pageable); /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index c40fd54bae..d1ea342bd2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/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.device.DeviceDao; 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 java.util.ArrayList; @@ -56,7 +56,7 @@ import java.util.UUID; @Component @SqlDao @Slf4j -public class JpaDeviceDao extends JpaAbstractSearchTextDao implements DeviceDao { +public class JpaDeviceDao extends JpaAbstractDao implements DeviceDao { @Autowired private DeviceRepository deviceRepository; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java index af526b8d64..aa3258287e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java @@ -82,6 +82,26 @@ public class JpaNotificationRuleDao extends JpaAbstractDao 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 protected Class getEntityClass() { return NotificationRuleEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java index 4bc6dada62..d976802fd2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java @@ -76,6 +76,26 @@ public class JpaNotificationTargetDao extends JpaAbstractDao 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 protected Class getEntityClass() { return NotificationTargetEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java index a83ca07c28..8a7cecb3d1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java +++ b/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.stereotype.Component; 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.notification.NotificationType; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; @@ -57,6 +58,26 @@ public class JpaNotificationTemplateDao extends JpaAbstractDao 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 protected JpaRepository getRepository() { return notificationTemplateRepository; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java index 2f2ffc4a43..1bdfe57bba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java +++ b/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.transaction.annotation.Transactional; 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.NotificationRuleInfoEntity; @@ -30,7 +31,7 @@ import java.util.List; import java.util.UUID; @Repository -public interface NotificationRuleRepository extends JpaRepository { +public interface NotificationRuleRepository extends JpaRepository, ExportableEntityRepository { 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"; @@ -59,4 +60,11 @@ public interface NotificationRuleRepository extends JpaRepository findByTenantId(UUID tenantId, Pageable pageable); + + @Query("SELECT externalId FROM NotificationRuleEntity WHERE id = :id") + UUID getExternalIdByInternal(@Param("id") UUID internalId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java index 7ba92db7d9..278fea696e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java +++ b/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.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.NotificationTargetEntity; import java.util.List; import java.util.UUID; @Repository -public interface NotificationTargetRepository extends JpaRepository { +public interface NotificationTargetRepository extends JpaRepository, ExportableEntityRepository { @Query("SELECT t FROM NotificationTargetEntity t WHERE t.tenantId = :tenantId " + "AND (:searchText = '' OR lower(t.name) LIKE lower(concat('%', :searchText, '%')))") @@ -52,4 +53,11 @@ public interface NotificationTargetRepository extends JpaRepository findByTenantId(UUID tenantId, Pageable pageable); + + @Query("SELECT externalId FROM NotificationTargetEntity WHERE id = :id") + UUID getExternalIdByInternal(@Param("id") UUID internalId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java index 1381c43710..73a25b98c3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java +++ b/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.transaction.annotation.Transactional; import org.thingsboard.server.common.data.notification.NotificationType; +import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity; import java.util.List; import java.util.UUID; @Repository -public interface NotificationTemplateRepository extends JpaRepository { +public interface NotificationTemplateRepository extends JpaRepository, ExportableEntityRepository { @Query("SELECT t FROM NotificationTemplateEntity t WHERE t.tenantId = :tenantId AND " + "t.notificationType IN :notificationTypes " + @@ -43,4 +44,11 @@ public interface NotificationTemplateRepository extends JpaRepository findByTenantId(UUID tenantId, Pageable pageable); + + @Query("SELECT externalId FROM NotificationTemplateEntity WHERE id = :id") + UUID getExternalIdByInternal(@Param("id") UUID internalId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 9d4ffd58b4..2135e98eed 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/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.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; @@ -66,6 +67,7 @@ import java.util.stream.Collectors; @Slf4j public class DefaultEntityQueryRepository implements EntityQueryRepository { private static final Map entityTableMap = new HashMap<>(); + private static final Map entityNameColumns = new HashMap<>(); 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"; 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.ASSET_PROFILE, "asset_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[]{ @@ -807,33 +827,38 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { private String entityNameQuery(QueryContext ctx, EntityNameFilter filter) { ctx.addStringParameter("entity_filter_name_filter", filter.getEntityNameFilter()); - + String nameColumn = getNameColumn(filter.getEntityType()); 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) { List types; String name; + String nameColumn; switch (filter.getType()) { case ASSET_TYPE: types = ((AssetTypeFilter) filter).getAssetTypes(); name = ((AssetTypeFilter) filter).getAssetNameFilter(); + nameColumn = getNameColumn(EntityType.ASSET); break; case DEVICE_TYPE: types = ((DeviceTypeFilter) filter).getDeviceTypes(); name = ((DeviceTypeFilter) filter).getDeviceNameFilter(); + nameColumn = getNameColumn(EntityType.DEVICE); break; case ENTITY_VIEW_TYPE: types = ((EntityViewTypeFilter) filter).getEntityViewTypes(); name = ((EntityViewTypeFilter) filter).getEntityViewNameFilter(); + nameColumn = getNameColumn(EntityType.ENTITY_VIEW); break; case EDGE_TYPE: types = ((EdgeTypeFilter) filter).getEdgeTypes(); name = ((EdgeTypeFilter) filter).getEdgeNameFilter(); + nameColumn = getNameColumn(EntityType.EDGE); break; default: throw new RuntimeException("Not supported!"); @@ -843,14 +868,23 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { if (!StringUtils.isEmpty(name)) { ctx.addStringParameter("entity_filter_type_query_name", name); 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 { 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) { switch (entityFilter.getType()) { case SINGLE_ENTITY: diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java index afc4b3661e..63a4ce0a77 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java +++ b/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()))); } + @Override + public void deleteEntity(TenantId tenantId, EntityId id) { + deleteWidgetsBundle(tenantId, (WidgetsBundleId) id); + } + @Override public EntityType getEntityType() { return EntityType.WIDGETS_BUNDLE; diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index ed1a124764..ad4770e04e 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -340,7 +340,6 @@ CREATE TABLE IF NOT EXISTS device ( type varchar(255), name varchar(255), label varchar(255), - search_text varchar(255), tenant_id uuid, firmware_id uuid, software_id uuid, @@ -797,7 +796,9 @@ CREATE TABLE IF NOT EXISTS notification_target ( tenant_id UUID NOT NULL, name VARCHAR(255) 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 ( @@ -807,7 +808,9 @@ CREATE TABLE IF NOT EXISTS notification_template ( name VARCHAR(255) NOT NULL, notification_type VARCHAR(50) 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 ( @@ -821,7 +824,9 @@ CREATE TABLE IF NOT EXISTS notification_rule ( trigger_config VARCHAR(1000) NOT NULL, recipients_config VARCHAR(10000) NOT NULL, additional_config VARCHAR(255), - CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name) + 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 ( diff --git a/dao/src/main/resources/sql/schema-views-and-functions.sql b/dao/src/main/resources/sql/schema-views-and-functions.sql index 2de757be54..eaa09d78cc 100644 --- a/dao/src/main/resources/sql/schema-views-and-functions.sql +++ b/dao/src/main/resources/sql/schema-views-and-functions.sql @@ -226,7 +226,11 @@ BEGIN END IF; IF NOT(existing.cleared) THEN 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; 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; diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java index ea35933d4d..25231f822f 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java @@ -202,7 +202,34 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest { @Test public void testClearAlarmProcedure() { 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 alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b"); Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM"); @@ -216,6 +243,7 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest { assertEquals(afterSave, result.getAlarm()); assertEquals(clearTs, result.getAlarm().getClearTs()); assertTrue(result.getAlarm().isCleared()); + assertEquals(alarm.getDetails(), result.getAlarm().getDetails()); result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode()); assertNotNull(result); assertNotNull(result.getAlarm()); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java index 880143b58a..f04331e01e 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java @@ -520,4 +520,11 @@ public class TestRestClient { .then() .statusCode(HTTP_OK); } + + public void setDevicePublic(DeviceId deviceId) { + given().spec(requestSpec) + .post("/api/customer/public/device/{deviceId}", deviceId.getId()) + .then() + .statusCode(HTTP_OK); + } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java index eb6776a4e4..4de6fbaf17 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java +++ b/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 HEIGHT = 1050; 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 JavascriptExecutor js; 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) { if (deviceId != null) { testRestClient.deleteDevice(deviceId); @@ -283,4 +290,19 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest { 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"); + } + } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CustomerPageElements.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CustomerPageElements.java index 465c3d918e..09edf9e852 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CustomerPageElements.java +++ b/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 HIDE_HOME_DASHBOARD_TOOLBAR = "//mat-checkbox[@formcontrolname='homeDashboardHideToolbar']//label"; 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_USER_ICON_HEADER = "Users"; private static final String CUSTOMER_ASSETS_ICON_HEADER = "Assets"; diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java index 030f7913c1..a4404858d9 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java +++ b/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 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 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 DESCRIPTION_FIELD_CREATE_VIEW = "//tb-device-wizard//textarea[@formcontrolname='description']"; 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 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_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_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 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_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_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_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) { return waitUntilElementToBeClickable(String.format(DEVICE, deviceName)); @@ -90,18 +90,10 @@ public class DevicePageElements extends OtherPageElementsHelper { return waitUntilElementToBeClickable(ADD_DEVICE_BTN); } - public WebElement nameField() { - return waitUntilElementToBeClickable(CREATE_DEVICE_NAME_FIELD); - } - public WebElement headerNameView() { return waitUntilVisibilityOfElementLocated(HEADER_NAME_VIEW); } - public WebElement descriptionFieldCreateField() { - return waitUntilElementToBeClickable(DESCRIPTION_FIELD_CREATE_VIEW); - } - public WebElement addDeviceView() { return waitUntilPresenceOfElementLocated(ADD_DEVICE_VIEW); } @@ -114,18 +106,10 @@ public class DevicePageElements extends OtherPageElementsHelper { return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_EDIT); } - public WebElement checkboxGatewayCreate() { - return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_CREATE); - } - public WebElement checkboxOverwriteActivityTimeEdit() { return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT); } - public WebElement checkboxOverwriteActivityTimeCreate() { - return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_CREATE); - } - public WebElement checkboxGatewayDetailsTab() { return waitUntilPresenceOfElementLocated(CHECKBOX_GATEWAY_DETAILS); } @@ -146,22 +130,10 @@ public class DevicePageElements extends OtherPageElementsHelper { return waitUntilElementToBeClickable(DEVICE_PROFILE_REDIRECTED_BTN); } - public WebElement deviceLabelFieldCreate() { - return waitUntilElementToBeClickable(DEVICE_LABEL_FIELD_CREATE); - } - public WebElement deviceLabelOnPage(String 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) { return waitUntilVisibilityOfElementLocated(String.format(DEVICE_CUSTOMER_PAGE, deviceName)); } @@ -173,4 +145,36 @@ public class DevicePageElements extends OtherPageElementsHelper { public WebElement deviceLabelDetailsField() { 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); + } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java index c6df90e0fa..3239bb5484 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java +++ b/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(); } - 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) { deleteBtn(deviceName).click(); warningPopUpYesBtn().click(); @@ -87,10 +75,30 @@ public class DevicePageHelper extends DevicePageElements { entityFromDropdown(deviceProfileName).click(); } - public void assignOnCustomer(String customerTitle) { - customerOptionBtn().click(); - assignOnCustomerField().click(); - entityFromList(customerTitle).click(); - sleep(2); //waiting for the action to count + public void unassignedDeviceByRightSideBtn(String deviceName) { + unassignBtn(deviceName).click(); + warningPopUpYesBtn().click(); + } + + 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(); } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabElements.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabElements.java new file mode 100644 index 0000000000..b688349230 --- /dev/null +++ b/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); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabHelper.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabHelper.java new file mode 100644 index 0000000000..3176dfb0f7 --- /dev/null +++ b/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(); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabElements.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabElements.java new file mode 100644 index 0000000000..53bb1a72da --- /dev/null +++ b/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); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabHelper.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabHelper.java new file mode 100644 index 0000000000..065f00cc08 --- /dev/null +++ b/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); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java index 3cd46fefc9..5b4f1c0685 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java +++ b/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.LoginPageHelper; import org.thingsboard.server.msa.ui.pages.SideBarMenuViewHelper; +import org.thingsboard.server.msa.ui.tabs.CreateDeviceTabHelper; @Epic("Device smoke tests") abstract public class AbstractDeviceTest extends AbstractDriverBaseTest { protected SideBarMenuViewHelper sideBarMenuView; protected DevicePageHelper devicePage; + protected CreateDeviceTabHelper createDeviceTab; protected String deviceName; + protected String deviceProfileTitle; @BeforeClass public void login() { new LoginPageHelper(driver).authorizationTenant(); sideBarMenuView = new SideBarMenuViewHelper(driver); devicePage = new DevicePageHelper(driver); + createDeviceTab = new CreateDeviceTabHelper(driver); } @AfterMethod public void delete() { deleteDeviceByName(deviceName); deviceName = null; + if (deviceProfileTitle != null) { + deleteDeviceProfileByTitle(deviceProfileTitle); + deviceProfileTitle = null; + } } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AssignToCustomerTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AssignToCustomerTest.java new file mode 100644 index 0000000000..8c0a45aa03 --- /dev/null +++ b/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))); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java index 3e4ab48f5d..7f13614b37 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java +++ b/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 org.testng.annotations.Test; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.msa.ui.pages.ProfilesPageElements; import org.thingsboard.server.msa.ui.utils.EntityPrototypes; import static org.assertj.core.api.Assertions.assertThat; @@ -39,8 +40,8 @@ public class CreateDeviceTest extends AbstractDeviceTest { sideBarMenuView.goToDevicesPage(); devicePage.openCreateDeviceView(); - devicePage.enterName(deviceName); - devicePage.addBtnC().click(); + createDeviceTab.enterName(deviceName); + createDeviceTab.addBtn().click(); devicePage.refreshBtn().click(); assertIsDisplayed(devicePage.entity(deviceName)); @@ -53,9 +54,9 @@ public class CreateDeviceTest extends AbstractDeviceTest { sideBarMenuView.goToDevicesPage(); devicePage.openCreateDeviceView(); - devicePage.enterName(deviceName); - devicePage.enterDescription(deviceName); - devicePage.addBtnC().click(); + createDeviceTab.enterName(deviceName); + createDeviceTab.enterDescription(deviceName); + createDeviceTab.addBtn().click(); devicePage.refreshBtn().click(); devicePage.entity(deviceName).click(); devicePage.setHeaderName(); @@ -70,8 +71,8 @@ public class CreateDeviceTest extends AbstractDeviceTest { public void createDeviceWithoutName() { sideBarMenuView.goToDevicesPage(); devicePage.openCreateDeviceView(); - devicePage.nameField().click(); - devicePage.addBtnC().click(); + createDeviceTab.nameField().click(); + createDeviceTab.addBtn().click(); assertIsDisplayed(devicePage.addDeviceView()); 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() { sideBarMenuView.goToDevicesPage(); devicePage.openCreateDeviceView(); - devicePage.enterName(" "); - devicePage.addBtnC().click(); + createDeviceTab.enterName(" "); + createDeviceTab.addBtn().click(); assertIsDisplayed(devicePage.warningMessage()); assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(EMPTY_DEVICE_MESSAGE); @@ -98,8 +99,8 @@ public class CreateDeviceTest extends AbstractDeviceTest { sideBarMenuView.goToDevicesPage(); devicePage.openCreateDeviceView(); - devicePage.enterName(deviceName); - devicePage.addBtnC().click(); + createDeviceTab.enterName(deviceName); + createDeviceTab.addBtn().click(); assertIsDisplayed(devicePage.warningMessage()); 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(); devicePage.openCreateDeviceView(); - devicePage.enterName(deviceName); - devicePage.addBtnC().click(); + createDeviceTab.enterName(deviceName); + createDeviceTab.addBtn().click(); assertIsDisplayed(devicePage.entity(deviceName)); } @@ -126,9 +127,9 @@ public class CreateDeviceTest extends AbstractDeviceTest { sideBarMenuView.goToDevicesPage(); devicePage.openCreateDeviceView(); - devicePage.enterName(deviceName); - devicePage.clearProfileFieldBtn().click(); - devicePage.addBtnC().click(); + createDeviceTab.enterName(deviceName); + createDeviceTab.clearProfileFieldBtn().click(); + createDeviceTab.addBtn().click(); assertIsDisplayed(devicePage.errorMessage()); 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(); devicePage.openCreateDeviceView(); - devicePage.enterName(deviceName); - devicePage.checkboxGatewayCreate().click(); - devicePage.addBtnC().click(); + createDeviceTab.enterName(deviceName); + createDeviceTab.checkboxGateway().click(); + createDeviceTab.addBtn().click(); assertIsDisplayed(devicePage.device(deviceName)); assertIsDisplayed(devicePage.checkboxGatewayPage(deviceName)); @@ -157,10 +158,10 @@ public class CreateDeviceTest extends AbstractDeviceTest { sideBarMenuView.goToDevicesPage(); devicePage.openCreateDeviceView(); - devicePage.enterName(deviceName); - devicePage.checkboxGatewayCreate().click(); - devicePage.checkboxOverwriteActivityTimeCreate().click(); - devicePage.addBtnC().click(); + createDeviceTab.enterName(deviceName); + createDeviceTab.checkboxGateway().click(); + createDeviceTab.checkboxOverwriteActivityTime().click(); + createDeviceTab.addBtn().click(); devicePage.device(deviceName).click(); assertThat(devicePage.checkboxOverwriteActivityTimeDetails().getAttribute("class").contains("selected")) @@ -175,9 +176,9 @@ public class CreateDeviceTest extends AbstractDeviceTest { sideBarMenuView.goToDevicesPage(); devicePage.openCreateDeviceView(); - devicePage.enterName(deviceName); - devicePage.enterLabel(deviceLabel); - devicePage.addBtnC().click(); + createDeviceTab.enterName(deviceName); + createDeviceTab.enterLabel(deviceLabel); + createDeviceTab.addBtn().click(); assertIsDisplayed(devicePage.deviceLabelOnPage(deviceName)); assertThat(devicePage.deviceLabelOnPage(deviceName).getText()).as("Label added correctly").isEqualTo(deviceLabel); @@ -191,9 +192,9 @@ public class CreateDeviceTest extends AbstractDeviceTest { sideBarMenuView.goToDevicesPage(); devicePage.openCreateDeviceView(); - devicePage.enterName(deviceName); - devicePage.assignOnCustomer(customer); - devicePage.addBtnC().click(); + createDeviceTab.enterName(deviceName); + createDeviceTab.assignOnCustomer(customer); + createDeviceTab.addBtn().click(); assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName)); assertThat(devicePage.deviceCustomerOnPage(deviceName).getText()) @@ -211,4 +212,42 @@ public class CreateDeviceTest extends AbstractDeviceTest { 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); + } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeleteSeveralDevicesTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeleteSeveralDevicesTest.java index 48df384c63..48b1921d0d 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeleteSeveralDevicesTest.java +++ b/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.msa.ui.utils.EntityPrototypes; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; 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") public void deleteSeveralDevicesByTopBtn() { sideBarMenuView.goToDevicesPage(); - devicePage.deleteSelected(2); + devicePage.deleteSelectedDevices(deviceName1, deviceName2); devicePage.refreshBtn().click(); - devicePage.assertEntityIsNotPresent(deviceName1); - devicePage.assertEntityIsNotPresent(deviceName2); + List.of(deviceName1, deviceName2) + .forEach(d -> devicePage.assertEntityIsNotPresent(d)); } @Test(groups = "smoke") @@ -76,9 +78,9 @@ public class DeleteSeveralDevicesTest extends AbstractDeviceTest { "that appears at the top without refresh") public void deleteSeveralWithoutRefresh() { sideBarMenuView.goToDevicesPage(); - devicePage.deleteSelected(2); + devicePage.deleteSelectedDevices(deviceName1, deviceName2); - devicePage.assertEntityIsNotPresent(deviceName1); - devicePage.assertEntityIsNotPresent(deviceName2); + List.of(deviceName1, deviceName2) + .forEach(d -> devicePage.assertEntityIsNotPresent(d)); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesByNameAndTypeLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesByNameAndTypeLoader.java index f8bf7089f3..e6b8377f51 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesByNameAndTypeLoader.java +++ b/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; 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.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.id.EntityId; @@ -32,7 +33,7 @@ public class EntitiesByNameAndTypeLoader { EntityType.USER); public static EntityId findEntityId(TbContext ctx, EntityType entityType, String entityName) { - SearchTextBasedWithAdditionalInfo targetEntity; + BaseData targetEntity; switch (entityType) { case DEVICE: targetEntity = ctx.getDeviceService().findDeviceByTenantIdAndName(ctx.getTenantId(), entityName); diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 9c3570310e..ee3e232bba 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -422,6 +422,14 @@ export class EntityService { pageLink.sortOrder.property = 'name'; entitiesObservable = this.notificationService.getNotificationTargets(pageLink, subType as NotificationType, config); 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; } diff --git a/ui-ngx/src/app/core/http/notification.service.ts b/ui-ngx/src/app/core/http/notification.service.ts index a92007643a..ec64621aeb 100644 --- a/ui-ngx/src/app/core/http/notification.service.ts +++ b/ui-ngx/src/app/core/http/notification.service.ts @@ -35,7 +35,7 @@ import { SlackConversation } from '@shared/models/notification.models'; import { User } from '@shared/models/user.model'; -import { isDefinedAndNotNull, isNotEmptyStr } from '@core/utils'; +import { isNotEmptyStr } from '@core/utils'; @Injectable({ providedIn: 'root' @@ -169,7 +169,7 @@ export class NotificationService { public getNotificationTemplates(pageLink: PageLink, notificationTypes?: NotificationType, config?: RequestConfig): Observable> { let url = `/api/notification/templates${pageLink.toQuery()}`; - if (isDefinedAndNotNull(notificationTypes)) { + if (isNotEmptyStr(notificationTypes)) { url += `¬ificationTypes=${notificationTypes}`; } return this.http.get>(url, defaultHttpOptionsFromConfig(config)); diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html index bd714d9e33..ef09a2b382 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html @@ -66,10 +66,10 @@ {{ 'version-control.export-credentials' | translate }} - + {{ 'version-control.export-attributes' | translate }} - + {{ 'version-control.export-relations' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.ts b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.ts index b41e51c5c2..b6a394982c 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.ts @@ -29,6 +29,7 @@ import { } from '@angular/forms'; import { PageComponent } from '@shared/components/page.component'; import { + entityTypesWithoutRelatedData, EntityTypeVersionCreateConfig, exportableEntityTypes, SyncStrategy, @@ -73,6 +74,7 @@ export class EntityTypesVersionCreateComponent extends PageComponent implements syncStrategyTranslations = syncStrategyTranslationMap; entityTypes = EntityType; + entityTypesWithoutRelatedData = entityTypesWithoutRelatedData; loading = true; diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html index 8a3b23ea5e..d179855a1a 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html @@ -66,10 +66,10 @@ {{ 'version-control.load-credentials' | translate }} - + {{ 'version-control.load-attributes' | translate }} - + {{ 'version-control.load-relations' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts index d4f28e3a7b..5405436f35 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts +++ b/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 { AbstractControl, ControlValueAccessor, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, UntypedFormArray, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, Validator, Validators } from '@angular/forms'; 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 { AppState } from '@core/core.state'; import { TranslateService } from '@ngx-translate/core'; 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 { RemoveOtherEntitiesConfirmComponent } from '@home/components/vc/remove-other-entities-confirm.component'; @@ -66,6 +70,7 @@ export class EntityTypesVersionLoadComponent extends PageComponent implements On public entityTypesVersionLoadFormGroup: UntypedFormGroup; entityTypes = EntityType; + entityTypesWithoutRelatedData = entityTypesWithoutRelatedData; loading = true; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 7b63176203..64ef1a6303 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/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 { Polygon } from './polygon'; 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 { WidgetContext } from '@home/models/widget-component.models'; import { @@ -148,7 +154,8 @@ export default abstract class LeafletMap { if (isDefinedAndNotNull(markerColor) && tinycolor(markerColor).isValid()) { const parsedColor = tinycolor(markerColor); return L.divIcon({ - html: `
` + + html: `
` + `
` + childCount + '
', iconSize: new L.Point(40, 40), 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'); @@ -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) { return null; } const lat = data[this.options.latKeyName]; 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 {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; } @@ -936,7 +944,7 @@ export default abstract class LeafletMap { return; } this.saveLocation(data, this.convertToCustomFormat(e.target._latlng)).subscribe(); - } + }; private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: Partial, updateBounds = true, callback?, snappable = false): Marker { @@ -1141,7 +1149,7 @@ export default abstract class LeafletMap { } } this.saveLocation(data, this.convertPolygonToCustomFormat(coordinates)).subscribe(() => {}); - } + }; createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: Partial, updateBounds = true, snappable = false) { @@ -1218,7 +1226,7 @@ export default abstract class LeafletMap { const center = e.layer.getLatLng(); const radius = e.layer.getRadius(); this.saveLocation(data, this.convertCircleToCustomFormat(center, radius)).subscribe(() => {}); - } + }; updateCircle(circlesData: FormattedData[], updateBounds = true) { const toDelete = new Set(Array.from(this.circles.keys())); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts index 4aad829eac..81005dee07 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts @@ -20,7 +20,7 @@ import { ShowTooltipAction, WidgetToolipSettings } from './map-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'; export function createTooltip(target: L.Layer, @@ -110,3 +110,16 @@ export function entitiesParseName(entities: FormattedData[], labelSettings: labe } 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); diff --git a/ui-ngx/src/app/shared/components/value-input.component.ts b/ui-ngx/src/app/shared/components/value-input.component.ts index 946d320241..4ba7abf157 100644 --- a/ui-ngx/src/app/shared/components/value-input.component.ts +++ b/ui-ngx/src/app/shared/components/value-input.component.ts @@ -80,6 +80,7 @@ export class ValueInputComponent implements OnInit, ControlValueAccessor { if (res) { this.modelValue = res; this.inputForm.control.patchValue({value: this.modelValue}); + this.updateView(); } } ); diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 062694cb43..1a5f187459 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -368,6 +368,8 @@ export const entityTypeTranslations = new Map, 'label'>{ +export interface NotificationRule extends Omit, 'label'>, ExportableEntity { tenantId: TenantId; enabled: boolean; templateId: NotificationTemplateId; @@ -235,7 +234,7 @@ export interface NonConfirmedNotificationEscalation { targets: Array; } -export interface NotificationTarget extends Omit, 'label'>{ +export interface NotificationTarget extends Omit, 'label'>, ExportableEntity { tenantId: TenantId; configuration: NotificationTargetConfig; } @@ -280,7 +279,7 @@ export const NotificationTargetTypeTranslationMap = new Map, 'label'>{ +export interface NotificationTemplate extends Omit, 'label'>, ExportableEntity { tenantId: TenantId; notificationType: NotificationType; configuration: NotificationTemplateConfig; diff --git a/ui-ngx/src/app/shared/models/vc.models.ts b/ui-ngx/src/app/shared/models/vc.models.ts index 155a9c8633..d1886fb1aa 100644 --- a/ui-ngx/src/app/shared/models/vc.models.ts +++ b/ui-ngx/src/app/shared/models/vc.models.ts @@ -30,9 +30,18 @@ export const exportableEntityTypes: Array = [ EntityType.DEVICE_PROFILE, EntityType.ASSET_PROFILE, EntityType.RULE_CHAIN, - EntityType.WIDGETS_BUNDLE + EntityType.WIDGETS_BUNDLE, + EntityType.NOTIFICATION_TEMPLATE, + EntityType.NOTIFICATION_TARGET, + EntityType.NOTIFICATION_RULE ]; +export const entityTypesWithoutRelatedData: Set = new Set([ + EntityType.NOTIFICATION_TEMPLATE, + EntityType.NOTIFICATION_TARGET, + EntityType.NOTIFICATION_RULE +]); + export interface VersionCreateConfig { saveRelations: boolean; saveAttributes: boolean; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 2a7b188fad..3496b5e65b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2071,9 +2071,15 @@ "type-queue": "Queue", "type-notification": "Notification", "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-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-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": { "created-time": "Created time",