Merge pull request #12905 from dashevchenko/resource_vc_fix

Version control: fixed resource restoring
This commit is contained in:
Viacheslav Klimov 2025-05-07 13:18:43 +03:00 committed by GitHub
commit 51636229d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 129 additions and 43 deletions

View File

@ -46,7 +46,7 @@ public class AssetImportService extends BaseEntityImportService<AssetId, Asset,
} }
@Override @Override
protected Asset saveOrUpdate(EntitiesImportCtx ctx, Asset asset, EntityExportData<Asset> exportData, IdProvider idProvider) { protected Asset saveOrUpdate(EntitiesImportCtx ctx, Asset asset, EntityExportData<Asset> exportData, IdProvider idProvider, CompareResult compareResult) {
Asset savedAsset = assetService.saveAsset(asset); Asset savedAsset = assetService.saveAsset(asset);
if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) { if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) {
importCalculatedFields(ctx, savedAsset, exportData, idProvider); importCalculatedFields(ctx, savedAsset, exportData, idProvider);

View File

@ -49,7 +49,7 @@ public class AssetProfileImportService extends BaseEntityImportService<AssetProf
} }
@Override @Override
protected AssetProfile saveOrUpdate(EntitiesImportCtx ctx, AssetProfile assetProfile, EntityExportData<AssetProfile> exportData, IdProvider idProvider) { protected AssetProfile saveOrUpdate(EntitiesImportCtx ctx, AssetProfile assetProfile, EntityExportData<AssetProfile> exportData, IdProvider idProvider, CompareResult compareResult) {
AssetProfile saved = assetProfileService.saveAssetProfile(assetProfile); AssetProfile saved = assetProfileService.saveAssetProfile(assetProfile);
if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) { if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) {
importCalculatedFields(ctx, saved, exportData, idProvider); importCalculatedFields(ctx, saved, exportData, idProvider);

View File

@ -18,6 +18,8 @@ package org.thingsboard.server.service.sync.ie.importing.impl;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.api.client.util.Objects; import com.google.api.client.util.Objects;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -117,10 +119,10 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
E prepared = prepare(ctx, entity, existingEntity, exportData, idProvider); E prepared = prepare(ctx, entity, existingEntity, exportData, idProvider);
boolean saveOrUpdate = existingEntity == null || compare(ctx, exportData, prepared, existingEntity); CompareResult compareResult = compare(ctx, exportData, prepared, existingEntity);
if (saveOrUpdate) { if (compareResult.isUpdateNeeded()) {
E savedEntity = saveOrUpdate(ctx, prepared, exportData, idProvider); E savedEntity = saveOrUpdate(ctx, prepared, exportData, idProvider, compareResult);
boolean created = existingEntity == null; boolean created = existingEntity == null;
importResult.setCreated(created); importResult.setCreated(created);
importResult.setUpdated(!created); importResult.setUpdated(!created);
@ -137,6 +139,17 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
return importResult; return importResult;
} }
@Data
@AllArgsConstructor
static class CompareResult {
private boolean updateNeeded;
private boolean externalIdChangedOnly;
public CompareResult(boolean updateNeeded) {
this.updateNeeded = updateNeeded;
}
}
protected boolean updateRelatedEntitiesIfUnmodified(EntitiesImportCtx ctx, E prepared, D exportData, IdProvider idProvider) { protected boolean updateRelatedEntitiesIfUnmodified(EntitiesImportCtx ctx, E prepared, D exportData, IdProvider idProvider) {
return importCalculatedFields(ctx, prepared, exportData, idProvider); return importCalculatedFields(ctx, prepared, exportData, idProvider);
} }
@ -148,18 +161,30 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
protected abstract E prepare(EntitiesImportCtx ctx, E entity, E oldEntity, D exportData, IdProvider idProvider); protected abstract E prepare(EntitiesImportCtx ctx, E entity, E oldEntity, D exportData, IdProvider idProvider);
protected boolean compare(EntitiesImportCtx ctx, D exportData, E prepared, E existing) { protected CompareResult compare(EntitiesImportCtx ctx, D exportData, E prepared, E existing) {
if (existing == null) {
log.debug("[{}] Found new entity.", prepared.getId());
return new CompareResult(true);
}
var newCopy = deepCopy(prepared); var newCopy = deepCopy(prepared);
var existingCopy = deepCopy(existing); var existingCopy = deepCopy(existing);
cleanupForComparison(newCopy); cleanupForComparison(newCopy);
cleanupForComparison(existingCopy); cleanupForComparison(existingCopy);
var result = !newCopy.equals(existingCopy); var updateNeeded = isUpdateNeeded(ctx, exportData, newCopy, existingCopy);
if (result) { boolean externalIdChangedOnly = false;
if (updateNeeded) {
log.debug("[{}] Found update.", prepared.getId()); log.debug("[{}] Found update.", prepared.getId());
log.debug("[{}] From: {}", prepared.getId(), newCopy); log.debug("[{}] From: {}", prepared.getId(), newCopy);
log.debug("[{}] To: {}", prepared.getId(), existingCopy); log.debug("[{}] To: {}", prepared.getId(), existingCopy);
cleanupExternalId(newCopy);
cleanupExternalId(existingCopy);
externalIdChangedOnly = newCopy.equals(existingCopy);
} }
return result; return new CompareResult(updateNeeded, externalIdChangedOnly);
}
protected boolean isUpdateNeeded(EntitiesImportCtx ctx, D exportData, E prepared, E existing) {
return !prepared.equals(existing);
} }
protected abstract E deepCopy(E e); protected abstract E deepCopy(E e);
@ -172,7 +197,11 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
} }
} }
protected abstract E saveOrUpdate(EntitiesImportCtx ctx, E entity, D exportData, IdProvider idProvider); protected void cleanupExternalId(E e) {
e.setExternalId(null);
}
protected abstract E saveOrUpdate(EntitiesImportCtx ctx, E entity, D exportData, IdProvider idProvider, CompareResult compareResult);
protected void processAfterSaved(EntitiesImportCtx ctx, EntityImportResult<E> importResult, D exportData, IdProvider idProvider) throws ThingsboardException { protected void processAfterSaved(EntitiesImportCtx ctx, EntityImportResult<E> importResult, D exportData, IdProvider idProvider) throws ThingsboardException {

View File

@ -52,7 +52,7 @@ public class CustomerImportService extends BaseEntityImportService<CustomerId, C
} }
@Override @Override
protected Customer saveOrUpdate(EntitiesImportCtx ctx, Customer customer, EntityExportData<Customer> exportData, IdProvider idProvider) { protected Customer saveOrUpdate(EntitiesImportCtx ctx, Customer customer, EntityExportData<Customer> exportData, IdProvider idProvider, CompareResult compareResult) {
if (!customer.isPublic()) { if (!customer.isPublic()) {
return customerService.saveCustomer(customer); return customerService.saveCustomer(customer);
} else { } else {

View File

@ -75,7 +75,7 @@ public class DashboardImportService extends BaseEntityImportService<DashboardId,
} }
@Override @Override
protected Dashboard saveOrUpdate(EntitiesImportCtx ctx, Dashboard dashboard, EntityExportData<Dashboard> exportData, IdProvider idProvider) { protected Dashboard saveOrUpdate(EntitiesImportCtx ctx, Dashboard dashboard, EntityExportData<Dashboard> exportData, IdProvider idProvider, CompareResult compareResult) {
var tenantId = ctx.getTenantId(); var tenantId = ctx.getTenantId();
Set<ShortCustomerInfo> assignedCustomers = Optional.ofNullable(dashboard.getAssignedCustomers()).orElse(Collections.emptySet()).stream() Set<ShortCustomerInfo> assignedCustomers = Optional.ofNullable(dashboard.getAssignedCustomers()).orElse(Collections.emptySet()).stream()
@ -116,8 +116,8 @@ public class DashboardImportService extends BaseEntityImportService<DashboardId,
} }
@Override @Override
protected boolean compare(EntitiesImportCtx ctx, EntityExportData<Dashboard> exportData, Dashboard prepared, Dashboard existing) { protected boolean isUpdateNeeded(EntitiesImportCtx ctx, EntityExportData<Dashboard> exportData, Dashboard prepared, Dashboard existing) {
return super.compare(ctx, exportData, prepared, existing) || !prepared.getConfiguration().equals(existing.getConfiguration()); return super.isUpdateNeeded(ctx, exportData, prepared, existing) || !prepared.getConfiguration().equals(existing.getConfiguration());
} }
@Override @Override

View File

@ -63,7 +63,7 @@ public class DeviceImportService extends BaseEntityImportService<DeviceId, Devic
} }
@Override @Override
protected Device saveOrUpdate(EntitiesImportCtx ctx, Device device, DeviceExportData exportData, IdProvider idProvider) { protected Device saveOrUpdate(EntitiesImportCtx ctx, Device device, DeviceExportData exportData, IdProvider idProvider, CompareResult compareResult) {
Device savedDevice; Device savedDevice;
if (exportData.getCredentials() != null && ctx.isSaveCredentials()) { if (exportData.getCredentials() != null && ctx.isSaveCredentials()) {
exportData.getCredentials().setId(null); exportData.getCredentials().setId(null);

View File

@ -51,7 +51,7 @@ public class DeviceProfileImportService extends BaseEntityImportService<DevicePr
} }
@Override @Override
protected DeviceProfile saveOrUpdate(EntitiesImportCtx ctx, DeviceProfile deviceProfile, EntityExportData<DeviceProfile> exportData, IdProvider idProvider) { protected DeviceProfile saveOrUpdate(EntitiesImportCtx ctx, DeviceProfile deviceProfile, EntityExportData<DeviceProfile> exportData, IdProvider idProvider, CompareResult compareResult) {
DeviceProfile saved = deviceProfileService.saveDeviceProfile(deviceProfile); DeviceProfile saved = deviceProfileService.saveDeviceProfile(deviceProfile);
if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) { if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) {
importCalculatedFields(ctx, saved, exportData, idProvider); importCalculatedFields(ctx, saved, exportData, idProvider);

View File

@ -55,7 +55,7 @@ public class EntityViewImportService extends BaseEntityImportService<EntityViewI
} }
@Override @Override
protected EntityView saveOrUpdate(EntitiesImportCtx ctx, EntityView entityView, EntityExportData<EntityView> exportData, IdProvider idProvider) { protected EntityView saveOrUpdate(EntitiesImportCtx ctx, EntityView entityView, EntityExportData<EntityView> exportData, IdProvider idProvider, CompareResult compareResult) {
return entityViewService.saveEntityView(entityView); return entityViewService.saveEntityView(entityView);
} }

View File

@ -135,7 +135,7 @@ public class NotificationRuleImportService extends BaseEntityImportService<Notif
} }
@Override @Override
protected NotificationRule saveOrUpdate(EntitiesImportCtx ctx, NotificationRule notificationRule, EntityExportData<NotificationRule> exportData, IdProvider idProvider) { protected NotificationRule saveOrUpdate(EntitiesImportCtx ctx, NotificationRule notificationRule, EntityExportData<NotificationRule> exportData, IdProvider idProvider, CompareResult compareResult) {
ConstraintValidator.validateFields(notificationRule); ConstraintValidator.validateFields(notificationRule);
return notificationRuleService.saveNotificationRule(ctx.getTenantId(), notificationRule); return notificationRuleService.saveNotificationRule(ctx.getTenantId(), notificationRule);
} }

View File

@ -80,7 +80,7 @@ public class NotificationTargetImportService extends BaseEntityImportService<Not
} }
@Override @Override
protected NotificationTarget saveOrUpdate(EntitiesImportCtx ctx, NotificationTarget notificationTarget, EntityExportData<NotificationTarget> exportData, IdProvider idProvider) { protected NotificationTarget saveOrUpdate(EntitiesImportCtx ctx, NotificationTarget notificationTarget, EntityExportData<NotificationTarget> exportData, IdProvider idProvider, CompareResult compareResult) {
ConstraintValidator.validateFields(notificationTarget); ConstraintValidator.validateFields(notificationTarget);
return notificationTargetService.saveNotificationTarget(ctx.getTenantId(), notificationTarget); return notificationTargetService.saveNotificationTarget(ctx.getTenantId(), notificationTarget);
} }

View File

@ -48,7 +48,7 @@ public class NotificationTemplateImportService extends BaseEntityImportService<N
} }
@Override @Override
protected NotificationTemplate saveOrUpdate(EntitiesImportCtx ctx, NotificationTemplate notificationTemplate, EntityExportData<NotificationTemplate> exportData, IdProvider idProvider) { protected NotificationTemplate saveOrUpdate(EntitiesImportCtx ctx, NotificationTemplate notificationTemplate, EntityExportData<NotificationTemplate> exportData, IdProvider idProvider, CompareResult compareResult) {
ConstraintValidator.validateFields(notificationTemplate); ConstraintValidator.validateFields(notificationTemplate);
return notificationTemplateService.saveNotificationTemplate(ctx.getTenantId(), notificationTemplate); return notificationTemplateService.saveNotificationTemplate(ctx.getTenantId(), notificationTemplate);
} }

View File

@ -57,22 +57,30 @@ public class ResourceImportService extends BaseEntityImportService<TbResourceId,
return existingResource; return existingResource;
} }
@Override
protected boolean compare(EntitiesImportCtx ctx, EntityExportData<TbResource> exportData, TbResource prepared, TbResource existing) {
return true;
}
@Override @Override
protected TbResource deepCopy(TbResource resource) { protected TbResource deepCopy(TbResource resource) {
return new TbResource(resource); return new TbResource(resource);
} }
@Override @Override
protected TbResource saveOrUpdate(EntitiesImportCtx ctx, TbResource resource, EntityExportData<TbResource> exportData, IdProvider idProvider) { protected void cleanupForComparison(TbResource resource) {
super.cleanupForComparison(resource);
resource.setSearchText(null);
if (resource.getDescriptor().isNull()) {
resource.setDescriptor(null);
}
}
@Override
protected TbResource saveOrUpdate(EntitiesImportCtx ctx, TbResource resource, EntityExportData<TbResource> exportData, IdProvider idProvider, CompareResult compareResult) {
if (resource.getResourceType() == ResourceType.IMAGE) { if (resource.getResourceType() == ResourceType.IMAGE) {
return new TbResource(imageService.saveImage(resource)); return new TbResource(imageService.saveImage(resource));
} else { } else {
resource = resourceService.saveResource(resource); if (compareResult.isExternalIdChangedOnly()) {
resource = resourceService.saveResource(resource, false);
} else {
resource = resourceService.saveResource(resource);
}
resource.setData(null); resource.setData(null);
resource.setPreview(null); resource.setPreview(null);
return resource; return resource;

View File

@ -18,6 +18,7 @@ package org.thingsboard.server.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.RuleChainExportData; import org.thingsboard.server.common.data.sync.ie.RuleChainExportData;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleNodeDao; import org.thingsboard.server.dao.rule.RuleNodeDao;
@ -103,7 +105,7 @@ public class RuleChainImportService extends BaseEntityImportService<RuleChainId,
} }
@Override @Override
protected RuleChain saveOrUpdate(EntitiesImportCtx ctx, RuleChain ruleChain, RuleChainExportData exportData, IdProvider idProvider) { protected RuleChain saveOrUpdate(EntitiesImportCtx ctx, RuleChain ruleChain, RuleChainExportData exportData, IdProvider idProvider, CompareResult compareResult) {
ruleChain = ruleChainService.saveRuleChain(ruleChain); ruleChain = ruleChainService.saveRuleChain(ruleChain);
if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) { if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) {
exportData.getMetaData().setRuleChainId(ruleChain.getId()); exportData.getMetaData().setRuleChainId(ruleChain.getId());
@ -115,15 +117,15 @@ public class RuleChainImportService extends BaseEntityImportService<RuleChainId,
} }
@Override @Override
protected boolean compare(EntitiesImportCtx ctx, RuleChainExportData exportData, RuleChain prepared, RuleChain existing) { protected boolean isUpdateNeeded(EntitiesImportCtx ctx, RuleChainExportData exportData, RuleChain prepared, RuleChain existing) {
boolean different = super.compare(ctx, exportData, prepared, existing); boolean updateNeeded = super.isUpdateNeeded(ctx, exportData, prepared, existing);
if (!different) { if (!updateNeeded) {
RuleChainMetaData newMD = exportData.getMetaData(); RuleChainMetaData newMD = exportData.getMetaData();
RuleChainMetaData existingMD = ruleChainService.loadRuleChainMetaData(ctx.getTenantId(), prepared.getId()); RuleChainMetaData existingMD = ruleChainService.loadRuleChainMetaData(ctx.getTenantId(), prepared.getId());
existingMD.setRuleChainId(null); existingMD.setRuleChainId(null);
different = !newMD.equals(existingMD); updateNeeded = !newMD.equals(existingMD);
} }
return different; return updateNeeded;
} }
@Override @Override

View File

@ -44,13 +44,13 @@ public class WidgetTypeImportService extends BaseEntityImportService<WidgetTypeI
} }
@Override @Override
protected WidgetTypeDetails saveOrUpdate(EntitiesImportCtx ctx, WidgetTypeDetails widgetsBundle, WidgetTypeExportData exportData, IdProvider idProvider) { protected WidgetTypeDetails saveOrUpdate(EntitiesImportCtx ctx, WidgetTypeDetails widgetsBundle, WidgetTypeExportData exportData, IdProvider idProvider, CompareResult compareResult) {
return widgetTypeService.saveWidgetType(widgetsBundle); return widgetTypeService.saveWidgetType(widgetsBundle);
} }
@Override @Override
protected boolean compare(EntitiesImportCtx ctx, WidgetTypeExportData exportData, WidgetTypeDetails prepared, WidgetTypeDetails existing) { protected CompareResult compare(EntitiesImportCtx ctx, WidgetTypeExportData exportData, WidgetTypeDetails prepared, WidgetTypeDetails existing) {
return true; return new CompareResult(true);
} }
@Override @Override

View File

@ -49,7 +49,7 @@ public class WidgetsBundleImportService extends BaseEntityImportService<WidgetsB
} }
@Override @Override
protected WidgetsBundle saveOrUpdate(EntitiesImportCtx ctx, WidgetsBundle widgetsBundle, WidgetsBundleExportData exportData, IdProvider idProvider) { protected WidgetsBundle saveOrUpdate(EntitiesImportCtx ctx, WidgetsBundle widgetsBundle, WidgetsBundleExportData exportData, IdProvider idProvider, CompareResult compareResult) {
if (CollectionsUtil.isNotEmpty(exportData.getWidgets())) { if (CollectionsUtil.isNotEmpty(exportData.getWidgets())) {
exportData.getWidgets().forEach(widgetTypeNode -> { exportData.getWidgets().forEach(widgetTypeNode -> {
String bundleAlias = widgetTypeNode.remove("bundleAlias").asText(); String bundleAlias = widgetTypeNode.remove("bundleAlias").asText();
@ -75,8 +75,8 @@ public class WidgetsBundleImportService extends BaseEntityImportService<WidgetsB
} }
@Override @Override
protected boolean compare(EntitiesImportCtx ctx, WidgetsBundleExportData exportData, WidgetsBundle prepared, WidgetsBundle existing) { protected CompareResult compare(EntitiesImportCtx ctx, WidgetsBundleExportData exportData, WidgetsBundle prepared, WidgetsBundle existing) {
return true; return new CompareResult(true);
} }
@Override @Override

View File

@ -454,7 +454,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
} }
private VersionLoadResult onError(EntityId externalId, Throwable e) { private VersionLoadResult onError(EntityId externalId, Throwable e) {
return analyze(e, externalId).orElse(VersionLoadResult.error(EntityLoadError.runtimeError(e))); return analyze(e, externalId).orElse(VersionLoadResult.error(EntityLoadError.runtimeError(e, externalId)));
} }
private Optional<VersionLoadResult> analyze(Throwable e, EntityId externalId) { private Optional<VersionLoadResult> analyze(Throwable e, EntityId externalId) {

View File

@ -40,6 +40,9 @@ import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
@ -113,6 +116,8 @@ import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.controller.TbResourceControllerTest.TEST_DATA;
import static org.thingsboard.server.controller.TbResourceControllerTest.JS_TEST_FILE_NAME;
@DaoSqlTest @DaoSqlTest
public class VersionControlTest extends AbstractControllerTest { public class VersionControlTest extends AbstractControllerTest {
@ -648,6 +653,26 @@ public class VersionControlTest extends AbstractControllerTest {
assertThat(importedCalculatedField.getType()).isEqualTo(calculatedField.getType()); assertThat(importedCalculatedField.getType()).isEqualTo(calculatedField.getType());
} }
@Test
public void testResourceVc_sameTenant() throws Exception {
TbResourceInfo resourceInfo = createResource("Test resource");
String versionId = createVersion("resources", EntityType.TB_RESOURCE);
TbResource resource = findResource(resourceInfo.getName());
loadVersion(versionId, EntityType.TB_RESOURCE);
TbResource importedResource = findResource(resource.getName());
checkImportedEntity(tenantId1, resource, tenantId1, importedResource);
checkImportedResourceData(resource, importedResource);
}
protected void checkImportedResourceData(TbResource resource, TbResource importedResource) {
assertThat(importedResource.getName()).isEqualTo(resource.getName());
assertThat(importedResource.getData()).isEqualTo(resource.getData());
assertThat(importedResource.getResourceKey()).isEqualTo(resource.getResourceKey());
assertThat(importedResource.getResourceType()).isEqualTo(resource.getResourceType());
}
private <E extends ExportableEntity<?> & HasTenantId> void checkImportedEntity(TenantId tenantId1, E initialEntity, TenantId tenantId2, E importedEntity) { private <E extends ExportableEntity<?> & HasTenantId> void checkImportedEntity(TenantId tenantId1, E initialEntity, TenantId tenantId2, E importedEntity) {
assertThat(initialEntity.getTenantId()).isEqualTo(tenantId1); assertThat(initialEntity.getTenantId()).isEqualTo(tenantId1);
assertThat(importedEntity.getTenantId()).isEqualTo(tenantId2); assertThat(importedEntity.getTenantId()).isEqualTo(tenantId2);
@ -1128,4 +1153,22 @@ public class VersionControlTest extends AbstractControllerTest {
return doGetTypedWithPageLink("/api/" + entityId.getEntityType() + "/" + entityId.getId() + "/calculatedFields?", new TypeReference<PageData<CalculatedField>>() {}, new PageLink(100, 0)).getData(); return doGetTypedWithPageLink("/api/" + entityId.getEntityType() + "/" + entityId.getId() + "/calculatedFields?", new TypeReference<PageData<CalculatedField>>() {}, new PageLink(100, 0)).getData();
} }
private TbResourceInfo createResource(String name) {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.JKS);
resource.setTitle(name);
resource.setFileName(JS_TEST_FILE_NAME);
resource.setEncodedData(TEST_DATA);
return saveTbResource(resource);
}
private TbResourceInfo saveTbResource(TbResource tbResource) {
return doPost("/api/resource", tbResource, TbResourceInfo.class);
}
private TbResource findResource(String name) throws Exception {
return doGetTypedWithPageLink("/api/resource?", new TypeReference<PageData<TbResource>>() {}, new PageLink(100, 0, name)).getData().get(0);
}
} }

View File

@ -45,11 +45,15 @@ public class EntityLoadError implements Serializable {
} }
public static EntityLoadError runtimeError(Throwable e) { public static EntityLoadError runtimeError(Throwable e) {
return runtimeError(e, null);
}
public static EntityLoadError runtimeError(Throwable e, EntityId externalId) {
String message = e.getMessage(); String message = e.getMessage();
if (StringUtils.isEmpty(message)) { if (StringUtils.isEmpty(message)) {
message = "unexpected error (" + ClassUtils.getShortClassName(e.getClass()) + ")"; message = "unexpected error (" + ClassUtils.getShortClassName(e.getClass()) + ")";
} }
return EntityLoadError.builder().type("RUNTIME").message(message).build(); return EntityLoadError.builder().type("RUNTIME").message(message).source(externalId).build();
} }
} }