Export/import of Widgets Bundles

This commit is contained in:
Viacheslav Klimov 2022-06-07 15:18:40 +03:00
parent e958cb0fab
commit fcd84f6277
16 changed files with 258 additions and 58 deletions

View File

@ -26,6 +26,8 @@ ALTER TABLE dashboard
ADD COLUMN IF NOT EXISTS external_id UUID;
ALTER TABLE customer
ADD COLUMN IF NOT EXISTS external_id UUID;
ALTER TABLE widgets_bundle
ADD COLUMN IF NOT EXISTS external_id UUID;
ALTER TABLE admin_settings
ADD COLUMN IF NOT EXISTS tenant_id uuid NOT NULL DEFAULT '13814000-1dd2-11b2-8080-808080808080';

View File

@ -310,7 +310,7 @@ public class EntitiesVersionControlController extends BaseController {
public DeferredResult<VersionLoadResult> loadEntitiesVersion(@RequestBody VersionLoadRequest request) throws ThingsboardException {
SecurityUser user = getCurrentUser();
try {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
accessControlService.checkPermission(user, Resource.VERSION_CONTROL, Operation.READ);
return wrapFuture(versionControlService.loadEntitiesVersion(user, request));
} catch (Exception e) {
throw handleException(e);

View File

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

View File

@ -63,7 +63,6 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
private final Map<EntityType, Dao<?>> daos = new HashMap<>();
private final EntityService entityService;
private final AccessControlService accessControlService;
@ -141,28 +140,6 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
}
private List<EntityId> findEntitiesByFilter(TenantId tenantId, CustomerId customerId, EntityFilter filter, int page, int pageSize) {
EntityDataPageLink pageLink = new EntityDataPageLink();
pageLink.setPage(page);
pageLink.setPageSize(pageSize);
EntityKey sortProperty = new EntityKey(EntityKeyType.ENTITY_FIELD, CREATED_TIME);
pageLink.setSortOrder(new EntityDataSortOrder(sortProperty, EntityDataSortOrder.Direction.DESC));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, List.of(sortProperty), Collections.emptyList(), Collections.emptyList());
return findEntitiesByQuery(tenantId, customerId, query);
}
private List<EntityId> findEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query) {
try {
return entityService.findEntityDataByQuery(tenantId, customerId, query).getData().stream()
.map(EntityData::getEntityId)
.collect(Collectors.toList());
} catch (DataAccessException e) {
log.error("Failed to find entity data by query: {}", e.getMessage());
throw new IllegalArgumentException("Entity filter cannot be processed");
}
}
@Override
public <I extends EntityId> void deleteByTenantIdAndId(TenantId tenantId, I id) {
EntityType entityType = id.getEntityType();

View File

@ -0,0 +1,60 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.sync.ie.exporting.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.sync.ie.EntityExportSettings;
import org.thingsboard.server.common.data.sync.ie.WidgetsBundleExportData;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.List;
import java.util.Set;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class WidgetsBundleExportService extends BaseEntityExportService<WidgetsBundleId, WidgetsBundle, WidgetsBundleExportData> {
private final WidgetTypeService widgetTypeService;
@Override
protected void setRelatedEntities(TenantId tenantId, WidgetsBundle widgetsBundle, WidgetsBundleExportData exportData, EntityExportSettings settings) {
if (widgetsBundle.getTenantId() == null || widgetsBundle.getTenantId().isNullUid()) {
throw new IllegalArgumentException("Export of system Widget Bundles is not allowed");
}
List<WidgetTypeDetails> widgets = widgetTypeService.findWidgetTypesDetailsByTenantIdAndBundleAlias(tenantId, widgetsBundle.getAlias());
exportData.setWidgets(widgets);
}
@Override
protected WidgetsBundleExportData newExportData() {
return new WidgetsBundleExportData();
}
@Override
public Set<EntityType> getSupportedEntityTypes() {
return Set.of(EntityType.WIDGETS_BUNDLE);
}
}

View File

@ -48,6 +48,7 @@ import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService;
@ -75,6 +76,8 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
protected EntityActionService entityActionService;
@Autowired
protected TbClusterService clusterService;
@Autowired
protected TbNotificationEntityService entityNotificationService;
@Transactional(rollbackFor = Exception.class)
@Override

View File

@ -0,0 +1,96 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.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.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.common.data.sync.ie.WidgetsBundleExportData;
import org.thingsboard.server.common.data.widget.BaseWidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class WidgetsBundleImportService extends BaseEntityImportService<WidgetsBundleId, WidgetsBundle, WidgetsBundleExportData> {
private final WidgetsBundleService widgetsBundleService;
private final WidgetTypeService widgetTypeService;
@Override
protected void setOwner(TenantId tenantId, WidgetsBundle widgetsBundle, IdProvider idProvider) {
widgetsBundle.setTenantId(tenantId);
}
@Override
protected WidgetsBundle prepareAndSave(TenantId tenantId, WidgetsBundle widgetsBundle, WidgetsBundleExportData exportData, IdProvider idProvider, EntityImportSettings importSettings) {
WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
if (widgetsBundle.getId() == null) {
for (WidgetTypeDetails widget : exportData.getWidgets()) {
widget.setId(null);
widget.setTenantId(tenantId);
widget.setBundleAlias(savedWidgetsBundle.getAlias());
widgetTypeService.saveWidgetType(widget);
}
} else {
Map<String, WidgetTypeInfo> existingWidgets = widgetTypeService.findWidgetTypesInfosByTenantIdAndBundleAlias(tenantId, savedWidgetsBundle.getAlias()).stream()
.collect(Collectors.toMap(BaseWidgetType::getAlias, w -> w));
for (WidgetTypeDetails widget : exportData.getWidgets()) {
WidgetTypeInfo existingWidget;
if ((existingWidget = existingWidgets.remove(widget.getAlias())) != null) {
widget.setId(existingWidget.getId());
widget.setCreatedTime(existingWidget.getCreatedTime());
} else {
widget.setId(null);
}
widget.setTenantId(tenantId);
widget.setBundleAlias(savedWidgetsBundle.getAlias());
widgetTypeService.saveWidgetType(widget);
}
existingWidgets.values().stream()
.map(BaseWidgetType::getId)
.forEach(widgetTypeId -> widgetTypeService.deleteWidgetType(tenantId, widgetTypeId));
}
return savedWidgetsBundle;
}
@Override
protected void onEntitySaved(SecurityUser user, WidgetsBundle savedWidgetsBundle, WidgetsBundle oldWidgetsBundle) throws ThingsboardException {
super.onEntitySaved(user, savedWidgetsBundle, oldWidgetsBundle);
entityNotificationService.notifySendMsgToEdgeService(user.getTenantId(), savedWidgetsBundle.getId(),
oldWidgetsBundle == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
}
@Override
public EntityType getEntityType() {
return EntityType.WIDGETS_BUNDLE;
}
}

View File

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@ -41,7 +42,8 @@ import java.lang.annotation.Target;
@Type(name = "DEVICE_PROFILE", value = DeviceProfile.class),
@Type(name = "ASSET", value = Asset.class),
@Type(name = "DASHBOARD", value = Dashboard.class),
@Type(name = "CUSTOMER", value = Customer.class)
@Type(name = "CUSTOMER", value = Customer.class),
@Type(name = "WIDGETS_BUNDLE", value = WidgetsBundle.class)
})
public @interface JsonTbEntity {
}

View File

@ -19,7 +19,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@ -31,7 +30,6 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.sync.JsonTbEntity;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@ -40,7 +38,8 @@ import java.util.Map;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "entityType", include = As.EXISTING_PROPERTY, visible = true, defaultImpl = EntityExportData.class)
@JsonSubTypes({
@Type(name = "DEVICE", value = DeviceExportData.class),
@Type(name = "RULE_CHAIN", value = RuleChainExportData.class)
@Type(name = "RULE_CHAIN", value = RuleChainExportData.class),
@Type(name = "WIDGETS_BUNDLE", value = WidgetsBundleExportData.class)
})
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data

View File

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.sync.ie;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.widget.BaseWidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import java.util.Comparator;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
public class WidgetsBundleExportData extends EntityExportData<WidgetsBundle> {
@JsonProperty(index = 3)
private List<WidgetTypeDetails> widgets;
@Override
public EntityExportData<WidgetsBundle> sort() {
super.sort();
widgets.sort(Comparator.comparing(BaseWidgetType::getAlias));
return this;
}
}

View File

@ -15,10 +15,13 @@
*/
package org.thingsboard.server.common.data.widget;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.TenantId;
@ -27,7 +30,8 @@ import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel
public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements HasTenantId {
@EqualsAndHashCode(callSuper = true)
public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements HasTenantId, ExportableEntity<WidgetsBundleId> {
private static final long serialVersionUID = -7627368878362410489L;
@ -63,6 +67,10 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
@ApiModelProperty(position = 7, value = "Description", readOnly = true)
private String description;
@Getter
@Setter
private WidgetsBundleId externalId;
public WidgetsBundle() {
super();
}
@ -78,6 +86,7 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
this.title = widgetsBundle.getTitle();
this.image = widgetsBundle.getImage();
this.description = widgetsBundle.getDescription();
this.externalId = widgetsBundle.getExternalId();
}
@ApiModelProperty(position = 1, value = "JSON object with the Widget Bundle Id. " +
@ -100,31 +109,10 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
return getTitle();
}
@JsonIgnore
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
result = 31 * result + (alias != null ? alias.hashCode() : 0);
result = 31 * result + (title != null ? title.hashCode() : 0);
result = 31 * result + (image != null ? image.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
return result;
}
@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;
WidgetsBundle that = (WidgetsBundle) o;
if (tenantId != null ? !tenantId.equals(that.tenantId) : that.tenantId != null) return false;
if (alias != null ? !alias.equals(that.alias) : that.alias != null) return false;
if (title != null ? !title.equals(that.title) : that.title != null) return false;
if (image != null ? !image.equals(that.image) : that.image != null) return false;
if (description != null ? !description.equals(that.description) : that.description != null) return false;
return true;
public String getName() {
return title;
}
@Override
@ -133,7 +121,6 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
sb.append("tenantId=").append(tenantId);
sb.append(", alias='").append(alias).append('\'');
sb.append(", title='").append(title).append('\'');
sb.append(", image='").append(image).append('\'');
sb.append(", description='").append(description).append('\'');
sb.append('}');
return sb.toString();

View File

@ -54,6 +54,9 @@ public final class WidgetsBundleEntity extends BaseSqlEntity<WidgetsBundle> impl
@Column(name = ModelConstants.WIDGETS_BUNDLE_DESCRIPTION)
private String description;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public WidgetsBundleEntity() {
super();
}
@ -70,6 +73,9 @@ public final class WidgetsBundleEntity extends BaseSqlEntity<WidgetsBundle> impl
this.title = widgetsBundle.getTitle();
this.image = widgetsBundle.getImage();
this.description = widgetsBundle.getDescription();
if (widgetsBundle.getExternalId() != null) {
this.externalId = widgetsBundle.getExternalId().getId();
}
}
@Override
@ -93,6 +99,9 @@ public final class WidgetsBundleEntity extends BaseSqlEntity<WidgetsBundle> impl
widgetsBundle.setTitle(title);
widgetsBundle.setImage(image);
widgetsBundle.setDescription(description);
if (externalId != null) {
widgetsBundle.setExternalId(new WidgetsBundleId(externalId));
}
return widgetsBundle;
}
}

View File

@ -93,4 +93,19 @@ public class JpaWidgetsBundleDao extends JpaAbstractSearchTextDao<WidgetsBundleE
return EntityType.WIDGETS_BUNDLE;
}
@Override
public WidgetsBundle findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(widgetsBundleRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public WidgetsBundle findByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(widgetsBundleRepository.findFirstByTenantIdAndTitle(tenantId, name));
}
@Override
public PageData<WidgetsBundle> findByTenantId(UUID tenantId, PageLink pageLink) {
return findTenantWidgetsBundlesByTenantId(tenantId, pageLink);
}
}

View File

@ -20,6 +20,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.WidgetsBundleEntity;
import java.util.UUID;
@ -27,7 +28,7 @@ import java.util.UUID;
/**
* Created by Valerii Sosliuk on 4/23/2017.
*/
public interface WidgetsBundleRepository extends JpaRepository<WidgetsBundleEntity, UUID> {
public interface WidgetsBundleRepository extends JpaRepository<WidgetsBundleEntity, UUID>, ExportableEntityRepository<WidgetsBundleEntity> {
WidgetsBundleEntity findWidgetsBundleByTenantIdAndAlias(UUID tenantId, String alias);
@ -49,4 +50,7 @@ public interface WidgetsBundleRepository extends JpaRepository<WidgetsBundleEnti
@Param("nullTenantId") UUID nullTenantId,
@Param("textSearch") String textSearch,
Pageable pageable);
WidgetsBundleEntity findFirstByTenantIdAndTitle(UUID tenantId, String title);
}

View File

@ -16,17 +16,19 @@
package org.thingsboard.server.dao.widget;
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.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import java.util.UUID;
/**
* The Interface WidgetsBundleDao.
*/
public interface WidgetsBundleDao extends Dao<WidgetsBundle> {
public interface WidgetsBundleDao extends Dao<WidgetsBundle>, ExportableEntityDao<WidgetsBundle> {
/**
* Save or update widgets bundle object

View File

@ -423,7 +423,8 @@ CREATE TABLE IF NOT EXISTS widgets_bundle (
tenant_id uuid,
title varchar(255),
image varchar(1000000),
description varchar(255)
description varchar(255),
external_id uuid
);
CREATE TABLE IF NOT EXISTS entity_view (