Browse Source

Merge with feature/entities-version-control

pull/6616/head
Viacheslav Klimov 4 years ago
parent
commit
468bc44d5d
  1. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  2. 1
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  3. 9
      application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java
  4. 12
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  5. 1
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
  6. 1
      application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java
  7. 1
      application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java
  8. 4
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java
  9. 3
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java
  10. 7
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java
  11. 3
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java
  12. 3
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java
  13. 12
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
  14. 3
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java
  15. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java
  16. 5
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java
  17. 3
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java
  18. 20
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ImportServiceException.java
  19. 30
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/MissingEntityException.java
  20. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java
  21. 334
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  22. 2
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
  23. 8
      application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java
  24. 32
      application/src/main/java/org/thingsboard/server/service/sync/vc/LoadEntityException.java
  25. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java
  26. 12
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java
  27. 1
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportSettings.java
  28. 1
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportSettings.java
  29. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/RuleChainExportData.java
  30. 1
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataInfo.java
  31. 42
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java
  32. 33
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityTypeLoadResult.java
  33. 29
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionLoadResult.java
  34. 1
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/VersionCreateConfig.java
  35. 1
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadConfig.java
  36. 3
      common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java
  37. 9
      ui-ngx/src/app/core/http/entities-version-control.service.ts
  38. 8
      ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html
  39. 4
      ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts
  40. 3
      ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.html
  41. 4
      ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.ts
  42. 8
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.html
  43. 4
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-create.component.ts
  44. 8
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.html
  45. 6
      ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts
  46. 10
      ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.html
  47. 8
      ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts
  48. 3
      ui-ngx/src/app/modules/home/components/vc/entity-version-diff.component.ts
  49. 19
      ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.html
  50. 32
      ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts
  51. 5
      ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts
  52. 1
      ui-ngx/src/app/modules/home/components/vc/version-control.component.html
  53. 3
      ui-ngx/src/app/modules/home/components/vc/version-control.component.ts
  54. 2
      ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html
  55. 2
      ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html
  56. 2
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-tabs.component.html
  57. 2
      ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html
  58. 2
      ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html
  59. 2
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html
  60. 12
      ui-ngx/src/app/shared/components/popover.component.ts
  61. 8
      ui-ngx/src/app/shared/components/popover.service.ts
  62. 22
      ui-ngx/src/app/shared/models/vc.models.ts
  63. 15
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -141,6 +141,7 @@ import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
@ -283,6 +284,9 @@ public abstract class BaseController {
@Autowired
protected QueueService queueService;
@Autowired
protected EntitiesVersionControlService vcService;
@Value("${server.log_controller_error_stack_trace}")
@Getter
private boolean logControllerErrorStackTrace;

1
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -220,6 +220,7 @@ public class DeviceProfileController extends BaseController {
}
}
DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
vcService.autoCommit(getCurrentUser(), savedDeviceProfile.getId());
tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);
tbClusterService.broadcastEntityStateChangeEvent(deviceProfile.getTenantId(), savedDeviceProfile.getId(),

9
application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java

@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.sync.vc.EntityDataDiff;
import org.thingsboard.server.common.data.sync.vc.EntityDataInfo;
import org.thingsboard.server.common.data.sync.vc.EntityVersion;
import org.thingsboard.server.common.data.sync.vc.VersionCreationResult;
import org.thingsboard.server.common.data.sync.vc.EntityTypeLoadResult;
import org.thingsboard.server.common.data.sync.vc.VersionLoadResult;
import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo;
import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest;
@ -238,12 +239,12 @@ public class EntitiesVersionControlController extends BaseController {
}
}
@GetMapping("/info/{versionId}/{entityType}/{internalEntityUuid}")
@GetMapping("/info/{versionId}/{entityType}/{externalEntityUuid}")
public DeferredResult<EntityDataInfo> getEntityDataInfo(@PathVariable String versionId,
@PathVariable EntityType entityType,
@PathVariable UUID internalEntityUuid) throws ThingsboardException {
@PathVariable UUID externalEntityUuid) throws ThingsboardException {
try {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, internalEntityUuid);
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid);
return wrapFuture(versionControlService.getEntityDataInfo(getCurrentUser(), entityId, versionId));
} catch (Exception e) {
throw handleException(e);
@ -296,7 +297,7 @@ public class EntitiesVersionControlController extends BaseController {
" }\n" +
"}\n```")
@PostMapping("/entity")
public DeferredResult<List<VersionLoadResult>> loadEntitiesVersion(@RequestBody VersionLoadRequest request) throws ThingsboardException {
public DeferredResult<VersionLoadResult> loadEntitiesVersion(@RequestBody VersionLoadRequest request) throws ThingsboardException {
SecurityUser user = getCurrentUser();
try {
return wrapFuture(versionControlService.loadEntitiesVersion(user, request));

12
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -83,6 +83,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -253,6 +254,7 @@ public class RuleChainController extends BaseController {
checkEntity(ruleChain.getId(), ruleChain, Resource.RULE_CHAIN);
RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain));
vcService.autoCommit(getCurrentUser(), savedRuleChain.getId());
if (RuleChainType.CORE.equals(savedRuleChain.getType())) {
tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), savedRuleChain.getId(),
@ -293,6 +295,7 @@ public class RuleChainController extends BaseController {
checkParameter(request.getName(), "name");
RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName());
vcService.autoCommit(getCurrentUser(), savedRuleChain.getId());
tbClusterService.broadcastEntityStateChangeEvent(savedRuleChain.getTenantId(), savedRuleChain.getId(), ComponentLifecycleEvent.CREATED);
@ -382,6 +385,15 @@ public class RuleChainController extends BaseController {
updatedRuleChains = Collections.emptyList();
}
if (updatedRuleChains.isEmpty()) {
vcService.autoCommit(getCurrentUser(), ruleChainMetaData.getRuleChainId());
} else {
List<UUID> uuids = new ArrayList<>(updatedRuleChains.size() + 1);
uuids.add(ruleChainMetaData.getRuleChainId().getId());
updatedRuleChains.forEach(rc -> uuids.add(rc.getId().getId()));
vcService.autoCommit(getCurrentUser(), EntityType.RULE_CHAIN, uuids);
}
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId()));
if (RuleChainType.CORE.equals(ruleChain.getType())) {

1
application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java

@ -46,6 +46,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
TenantId tenantId = asset.getTenantId();
try {
Asset savedAsset = checkNotNull(assetService.saveAsset(asset));
vcService.autoCommit(user, savedAsset.getId());
notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedAsset.getId(), asset, savedAsset.getCustomerId(), actionType, user);
return savedAsset;
} catch (Exception e) {

1
application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java

@ -45,6 +45,7 @@ public class DefaultTbCustomerService extends AbstractTbEntityService implements
TenantId tenantId = customer.getTenantId();
try {
Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer));
vcService.autoCommit(user, savedCustomer.getId());
notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedCustomer.getId(), savedCustomer, savedCustomer.getId(), actionType, user);
return savedCustomer;
} catch (Exception e) {

1
application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java

@ -54,6 +54,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T
ActionType actionType = device.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
try {
Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken));
vcService.autoCommit(user, savedDevice.getId());
notificationEntityService.notifyCreateOrUpdateDevice(tenantId, savedDevice.getId(), savedDevice.getCustomerId(),
savedDevice, oldDevice, actionType, user);

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

@ -30,11 +30,11 @@ public abstract class BaseEntityExportService<I extends EntityId, E extends Expo
@Override
protected void setAdditionalExportData(SecurityUser user, E entity, D exportData, EntityExportSettings exportSettings) throws ThingsboardException {
setRelatedEntities(user.getTenantId(), entity, (D) exportData);
setRelatedEntities(user.getTenantId(), entity, (D) exportData, exportSettings);
super.setAdditionalExportData(user, entity, exportData, exportSettings);
}
protected void setRelatedEntities(TenantId tenantId, E mainEntity, D exportData) {}
protected void setRelatedEntities(TenantId tenantId, E mainEntity, D exportData, EntityExportSettings settings) {}
protected abstract D newExportData();

3
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java

@ -40,6 +40,7 @@ import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesServic
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
@ -111,7 +112,7 @@ public class DefaultEntityExportService<I extends EntityId, E extends Exportable
} else {
scopes = Collections.singletonList(DataConstants.SERVER_SCOPE);
}
Map<String, List<AttributeExportData>> attributes = new HashMap<>();
Map<String, List<AttributeExportData>> attributes = new LinkedHashMap<>();
scopes.forEach(scope -> {
try {
attributes.put(scope, attributesService.findAll(user.getTenantId(), entity.getId(), scope).get().stream()

7
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.ie.EntityExportSettings;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.common.data.sync.ie.DeviceExportData;
@ -35,8 +36,10 @@ public class DeviceExportService extends BaseEntityExportService<DeviceId, Devic
private final DeviceCredentialsService deviceCredentialsService;
@Override
protected void setRelatedEntities(TenantId tenantId, Device device, DeviceExportData exportData) {
exportData.setCredentials(deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId()));
protected void setRelatedEntities(TenantId tenantId, Device device, DeviceExportData exportData, EntityExportSettings settings) {
if (settings.isExportCredentials()) {
exportData.setCredentials(deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId()));
}
}
@Override

3
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.sync.ie.EntityExportSettings;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.common.data.sync.ie.RuleChainExportData;
@ -35,7 +36,7 @@ public class RuleChainExportService extends BaseEntityExportService<RuleChainId,
private final RuleChainService ruleChainService;
@Override
protected void setRelatedEntities(TenantId tenantId, RuleChain ruleChain, RuleChainExportData exportData) {
protected void setRelatedEntities(TenantId tenantId, RuleChain ruleChain, RuleChainExportData exportData, EntityExportSettings exportSettings) {
exportData.setMetaData(ruleChainService.loadRuleChainMetaData(tenantId, ruleChain.getId()));
}

3
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -42,7 +43,7 @@ public class AssetImportService extends BaseEntityImportService<AssetId, Asset,
}
@Override
protected Asset prepareAndSave(TenantId tenantId, Asset asset, EntityExportData<Asset> exportData, IdProvider idProvider) {
protected Asset prepareAndSave(TenantId tenantId, Asset asset, EntityExportData<Asset> exportData, IdProvider idProvider, EntityImportSettings importSettings) {
return assetService.saveAsset(asset);
}

12
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java

@ -64,7 +64,8 @@ import java.util.stream.Collectors;
@Slf4j
public abstract class BaseEntityImportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> implements EntityImportService<I, E, D> {
@Autowired @Lazy
@Autowired
@Lazy
private ExportableEntitiesService exportableEntitiesService;
@Autowired
private RelationService relationService;
@ -95,7 +96,7 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
exportableEntitiesService.checkPermission(user, existingEntity, getEntityType(), Operation.WRITE);
}
E savedEntity = prepareAndSave(user.getTenantId(), entity, exportData, idProvider);
E savedEntity = prepareAndSave(user.getTenantId(), entity, exportData, idProvider, importSettings);
importResult.setSavedEntity(savedEntity);
importResult.setOldEntity(existingEntity);
@ -108,7 +109,7 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
protected abstract void setOwner(TenantId tenantId, E entity, IdProvider idProvider);
protected abstract E prepareAndSave(TenantId tenantId, E entity, D exportData, IdProvider idProvider);
protected abstract E prepareAndSave(TenantId tenantId, E entity, D exportData, IdProvider idProvider, EntityImportSettings importSettings);
protected void processAfterSaved(SecurityUser user, EntityImportResult<E> importResult, D exportData,
@ -210,7 +211,8 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
// fixme: attributes are saved outside the transaction
tsSubService.saveAndNotify(user.getTenantId(), entity.getId(), scope, attributeKvEntries, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void unused) {}
public void onSuccess(@Nullable Void unused) {
}
@Override
public void onFailure(Throwable thr) {
@ -246,7 +248,7 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
private <ID extends EntityId> HasId<ID> findInternalEntity(TenantId tenantId, ID externalId) {
return (HasId<ID>) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(tenantId, externalId))
.or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(tenantId, externalId)))
.orElseThrow(() -> new IllegalArgumentException("Cannot find " + externalId.getEntityType() + " by external id " + externalId));
.orElseThrow(() -> new MissingEntityException(externalId));
}

3
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -41,7 +42,7 @@ public class CustomerImportService extends BaseEntityImportService<CustomerId, C
}
@Override
protected Customer prepareAndSave(TenantId tenantId, Customer customer, EntityExportData<Customer> exportData, IdProvider idProvider) {
protected Customer prepareAndSave(TenantId tenantId, Customer customer, EntityExportData<Customer> exportData, IdProvider idProvider, EntityImportSettings importSettings) {
if (customer.isPublic()) {
return customerService.findOrCreatePublicCustomer(tenantId);
} else {

2
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java

@ -67,7 +67,7 @@ public class DashboardImportService extends BaseEntityImportService<DashboardId,
}
@Override
protected Dashboard prepareAndSave(TenantId tenantId, Dashboard dashboard, EntityExportData<Dashboard> exportData, IdProvider idProvider) {
protected Dashboard prepareAndSave(TenantId tenantId, Dashboard dashboard, EntityExportData<Dashboard> exportData, IdProvider idProvider, EntityImportSettings importSettings) {
JsonNode configuration = dashboard.getConfiguration();
JsonNode entityAliases = configuration.get("entityAliases");
if (entityAliases != null && entityAliases.isObject()) {

5
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java

@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -41,11 +42,11 @@ public class DeviceImportService extends BaseEntityImportService<DeviceId, Devic
}
@Override
protected Device prepareAndSave(TenantId tenantId, Device device, DeviceExportData exportData, IdProvider idProvider) {
protected Device prepareAndSave(TenantId tenantId, Device device, DeviceExportData exportData, IdProvider idProvider, EntityImportSettings importSettings) {
device.setDeviceProfileId(idProvider.getInternalId(device.getDeviceProfileId()));
device.setFirmwareId(idProvider.getInternalId(device.getFirmwareId()));
device.setSoftwareId(idProvider.getInternalId(device.getSoftwareId()));
if (exportData.getCredentials() != null) {
if (exportData.getCredentials() != null && importSettings.isSaveCredentials()) {
exportData.getCredentials().setId(null);
exportData.getCredentials().setDeviceId(null);
return deviceService.saveDeviceWithCredentials(device, exportData.getCredentials());

3
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.ota.OtaPackageStateService;
@ -46,7 +47,7 @@ public class DeviceProfileImportService extends BaseEntityImportService<DevicePr
}
@Override
protected DeviceProfile prepareAndSave(TenantId tenantId, DeviceProfile deviceProfile, EntityExportData<DeviceProfile> exportData, IdProvider idProvider) {
protected DeviceProfile prepareAndSave(TenantId tenantId, DeviceProfile deviceProfile, EntityExportData<DeviceProfile> exportData, IdProvider idProvider, EntityImportSettings importSettings) {
deviceProfile.setDefaultRuleChainId(idProvider.getInternalId(deviceProfile.getDefaultRuleChainId()));
deviceProfile.setDefaultDashboardId(idProvider.getInternalId(deviceProfile.getDefaultDashboardId()));
deviceProfile.setFirmwareId(idProvider.getInternalId(deviceProfile.getFirmwareId()));

20
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ImportServiceException.java

@ -0,0 +1,20 @@
/**
* 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;
public class ImportServiceException extends RuntimeException{
private static final long serialVersionUID = -4932715239522125041L;
}

30
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/MissingEntityException.java

@ -0,0 +1,30 @@
/**
* 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.Getter;
import org.thingsboard.server.common.data.id.EntityId;
public class MissingEntityException extends ImportServiceException {
private static final long serialVersionUID = 3669135386955906022L;
@Getter
private final EntityId entityId;
public MissingEntityException(EntityId entityId) {
this.entityId = entityId;
}
}

2
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java

@ -61,7 +61,7 @@ public class RuleChainImportService extends BaseEntityImportService<RuleChainId,
}
@Override
protected RuleChain prepareAndSave(TenantId tenantId, RuleChain ruleChain, RuleChainExportData exportData, IdProvider idProvider) {
protected RuleChain prepareAndSave(TenantId tenantId, RuleChain ruleChain, RuleChainExportData exportData, IdProvider idProvider, EntityImportSettings importSettings) {
RuleChainMetaData metaData = exportData.getMetaData();
Optional.ofNullable(metaData.getNodes()).orElse(Collections.emptyList())
.forEach(ruleNode -> {

334
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java

@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
@ -46,6 +47,8 @@ import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.common.data.sync.vc.EntityDataDiff;
import org.thingsboard.server.common.data.sync.vc.EntityDataInfo;
import org.thingsboard.server.common.data.sync.vc.EntityLoadError;
import org.thingsboard.server.common.data.sync.vc.EntityTypeLoadResult;
import org.thingsboard.server.common.data.sync.vc.EntityVersion;
import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
import org.thingsboard.server.common.data.sync.vc.VersionCreationResult;
@ -53,6 +56,7 @@ import org.thingsboard.server.common.data.sync.vc.VersionLoadResult;
import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo;
import org.thingsboard.server.common.data.sync.vc.request.create.AutoVersionCreateConfig;
import org.thingsboard.server.common.data.sync.vc.request.create.ComplexVersionCreateRequest;
import org.thingsboard.server.common.data.sync.vc.request.create.EntityTypeVersionCreateConfig;
import org.thingsboard.server.common.data.sync.vc.request.create.SingleEntityVersionCreateRequest;
import org.thingsboard.server.common.data.sync.vc.request.create.SyncStrategy;
import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateConfig;
@ -63,12 +67,14 @@ import org.thingsboard.server.common.data.sync.vc.request.load.SingleEntityVersi
import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadConfig;
import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadRequest;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
import org.thingsboard.server.queue.util.TbCoreComponent;
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.EntitiesExportImportService;
import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService;
import org.thingsboard.server.service.sync.ie.importing.impl.MissingEntityException;
import org.thingsboard.server.service.sync.vc.autocommit.TbAutoCommitSettingsService;
import org.thingsboard.server.service.sync.vc.data.CommitGitRequest;
import org.thingsboard.server.service.sync.vc.repository.TbRepositorySettingsService;
@ -77,13 +83,17 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static com.google.common.util.concurrent.Futures.transform;
import static com.google.common.util.concurrent.Futures.transformAsync;
@ -169,6 +179,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
EntityExportData<ExportableEntity<EntityId>> entityData = exportImportService.exportEntity(user, entityId, EntityExportSettings.builder()
.exportRelations(config.isSaveRelations())
.exportAttributes(config.isSaveAttributes())
.exportCredentials(config.isSaveCredentials())
.build());
return gitServiceQueue.addToCommit(commit, entityData);
}
@ -200,149 +211,185 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
@SuppressWarnings({"UnstableApiUsage", "rawtypes"})
@Override
public ListenableFuture<List<VersionLoadResult>> loadEntitiesVersion(SecurityUser user, VersionLoadRequest request) throws Exception {
public ListenableFuture<VersionLoadResult> loadEntitiesVersion(SecurityUser user, VersionLoadRequest request) throws Exception {
switch (request.getType()) {
case SINGLE_ENTITY: {
SingleEntityVersionLoadRequest versionLoadRequest = (SingleEntityVersionLoadRequest) request;
VersionLoadConfig config = versionLoadRequest.getConfig();
ListenableFuture<EntityExportData> future = gitServiceQueue.getEntity(user.getTenantId(), request.getVersionId(), versionLoadRequest.getExternalEntityId());
return Futures.transform(future, entityData -> {
EntityImportResult<?> importResult = transactionTemplate.execute(status -> {
try {
return exportImportService.importEntity(user, entityData, EntityImportSettings.builder()
.updateRelations(config.isLoadRelations())
.saveAttributes(config.isLoadAttributes())
.findExistingByName(false)
.build(), true, true);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
return List.of(VersionLoadResult.builder()
.entityType(importResult.getEntityType())
.created(importResult.getOldEntity() == null ? 1 : 0)
.updated(importResult.getOldEntity() != null ? 1 : 0)
.deleted(0)
.build());
}, executor);
return Futures.transform(future, entityData -> doInTemplate(status -> loadSingleEntity(user, config, entityData)), executor);
}
case ENTITY_TYPE: {
EntityTypeVersionLoadRequest versionLoadRequest = (EntityTypeVersionLoadRequest) request;
return executor.submit(() -> transactionTemplate.execute(status -> {
Map<EntityType, VersionLoadResult> results = new HashMap<>();
Map<EntityType, Set<EntityId>> importedEntities = new HashMap<>();
Map<EntityId, EntityImportSettings> toReimport = new HashMap<>();
List<ThrowingRunnable> saveReferencesCallbacks = new ArrayList<>();
List<ThrowingRunnable> sendEventsCallbacks = new ArrayList<>();
versionLoadRequest.getEntityTypes().keySet().stream()
.sorted(exportImportService.getEntityTypeComparatorForImport())
.forEach(entityType -> {
EntityTypeVersionLoadConfig config = versionLoadRequest.getEntityTypes().get(entityType);
AtomicInteger created = new AtomicInteger();
AtomicInteger updated = new AtomicInteger();
return executor.submit(() -> doInTemplate(status -> loadMultipleEntities(user, versionLoadRequest)));
}
default:
throw new IllegalArgumentException("Unsupported version load request");
}
}
try {
int limit = 100;
int offset = 0;
List<EntityExportData> entityDataList;
do {
entityDataList = gitServiceQueue.getEntities(user.getTenantId(), request.getVersionId(), entityType, offset, limit).get();
EntityImportSettings importSettings = EntityImportSettings.builder()
.updateRelations(config.isLoadRelations())
.saveAttributes(config.isLoadAttributes())
.findExistingByName(config.isFindExistingEntityByName())
.build();
for (EntityExportData entityData : entityDataList) {
EntityImportResult<?> importResult = exportImportService.importEntity(user, entityData,
importSettings, false, false);
if (importResult.getUpdatedAllExternalIds() != null && !importResult.getUpdatedAllExternalIds()) {
toReimport.put(entityData.getEntity().getExternalId(), importSettings);
continue;
}
if (importResult.getOldEntity() == null) created.incrementAndGet();
else updated.incrementAndGet();
saveReferencesCallbacks.add(importResult.getSaveReferencesCallback());
sendEventsCallbacks.add(importResult.getSendEventsCallback());
importedEntities.computeIfAbsent(entityType, t -> new HashSet<>())
.add(importResult.getSavedEntity().getId());
}
offset += limit;
} while (entityDataList.size() == limit);
} catch (Exception e) {
throw new RuntimeException(e);
}
results.put(entityType, VersionLoadResult.builder()
.entityType(entityType)
.created(created.get())
.updated(updated.get())
.build());
});
private VersionLoadResult doInTemplate(TransactionCallback<VersionLoadResult> result) {
try {
return transactionTemplate.execute(result);
} catch (LoadEntityException e) {
return onError(e.getData(), e.getCause());
}
}
private VersionLoadResult loadSingleEntity(SecurityUser user, VersionLoadConfig config, EntityExportData entityData) {
try {
EntityImportResult<?> importResult = exportImportService.importEntity(user, entityData,
EntityImportSettings.builder()
.updateRelations(config.isLoadRelations())
.saveAttributes(config.isLoadAttributes())
.saveCredentials(config.isLoadCredentials())
.findExistingByName(false)
.build(), true, true);
return VersionLoadResult.success(EntityTypeLoadResult.builder()
.entityType(importResult.getEntityType())
.created(importResult.getOldEntity() == null ? 1 : 0)
.updated(importResult.getOldEntity() != null ? 1 : 0)
.deleted(0)
.build());
} catch (Exception e) {
throw new LoadEntityException(entityData, e);
}
}
private VersionLoadResult loadMultipleEntities(SecurityUser user, EntityTypeVersionLoadRequest request) {
Map<EntityType, EntityTypeLoadResult> results = new HashMap<>();
Map<EntityType, Set<EntityId>> importedEntities = new HashMap<>();
Map<EntityId, EntityImportSettings> toReimport = new HashMap<>();
List<ThrowingRunnable> saveReferencesCallbacks = new ArrayList<>();
List<ThrowingRunnable> sendEventsCallbacks = new ArrayList<>();
List<EntityType> entityTypes = request.getEntityTypes().keySet().stream()
.sorted(exportImportService.getEntityTypeComparatorForImport()).collect(Collectors.toList());
for (EntityType entityType : entityTypes) {
EntityTypeVersionLoadConfig config = request.getEntityTypes().get(entityType);
AtomicInteger created = new AtomicInteger();
AtomicInteger updated = new AtomicInteger();
int limit = 100;
int offset = 0;
List<EntityExportData> entityDataList;
do {
try {
entityDataList = gitServiceQueue.getEntities(user.getTenantId(), request.getVersionId(), entityType, offset, limit).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
EntityImportSettings importSettings = EntityImportSettings.builder()
.updateRelations(config.isLoadRelations())
.saveAttributes(config.isLoadAttributes())
.findExistingByName(config.isFindExistingEntityByName())
.build();
for (EntityExportData entityData : entityDataList) {
EntityImportResult<?> importResult;
try {
importResult = exportImportService.importEntity(user, entityData,
importSettings, false, false);
} catch (Exception e) {
throw new LoadEntityException(entityData, e);
}
if (importResult.getUpdatedAllExternalIds() != null && !importResult.getUpdatedAllExternalIds()) {
toReimport.put(entityData.getEntity().getExternalId(), importSettings);
continue;
}
if (importResult.getOldEntity() == null) created.incrementAndGet();
else updated.incrementAndGet();
saveReferencesCallbacks.add(importResult.getSaveReferencesCallback());
sendEventsCallbacks.add(importResult.getSendEventsCallback());
toReimport.forEach((externalId, importSettings) -> {
try {
EntityExportData entityData = gitServiceQueue.getEntity(user.getTenantId(), request.getVersionId(), externalId).get();
importSettings.setResetExternalIdsOfAnotherTenant(true);
EntityImportResult<?> importResult = exportImportService.importEntity(user, entityData,
importSettings, false, false);
VersionLoadResult stats = results.get(externalId.getEntityType());
if (importResult.getOldEntity() == null) stats.setCreated(stats.getCreated() + 1);
else stats.setUpdated(stats.getUpdated() + 1);
saveReferencesCallbacks.add(importResult.getSaveReferencesCallback());
sendEventsCallbacks.add(importResult.getSendEventsCallback());
importedEntities.computeIfAbsent(externalId.getEntityType(), t -> new HashSet<>())
.add(importResult.getSavedEntity().getId());
} catch (Exception e) {
throw new RuntimeException(e);
importedEntities.computeIfAbsent(entityType, t -> new HashSet<>())
.add(importResult.getSavedEntity().getId());
}
offset += limit;
} while (entityDataList.size() == limit);
results.put(entityType, EntityTypeLoadResult.builder()
.entityType(entityType)
.created(created.get())
.updated(updated.get())
.build());
}
toReimport.forEach((externalId, importSettings) -> {
try {
EntityExportData entityData = gitServiceQueue.getEntity(user.getTenantId(), request.getVersionId(), externalId).get();
importSettings.setResetExternalIdsOfAnotherTenant(true);
EntityImportResult<?> importResult = exportImportService.importEntity(user, entityData,
importSettings, false, false);
EntityTypeLoadResult stats = results.get(externalId.getEntityType());
if (importResult.getOldEntity() == null) stats.setCreated(stats.getCreated() + 1);
else stats.setUpdated(stats.getUpdated() + 1);
saveReferencesCallbacks.add(importResult.getSaveReferencesCallback());
sendEventsCallbacks.add(importResult.getSendEventsCallback());
importedEntities.computeIfAbsent(externalId.getEntityType(), t -> new HashSet<>())
.add(importResult.getSavedEntity().getId());
} catch (Exception e) {
throw new RuntimeException(e);
}
});
request.getEntityTypes().keySet().stream()
.filter(entityType -> request.getEntityTypes().get(entityType).isRemoveOtherEntities())
.sorted(exportImportService.getEntityTypeComparatorForImport().reversed())
.forEach(entityType -> {
DaoUtil.processInBatches(pageLink -> {
return exportableEntitiesService.findEntitiesByTenantId(user.getTenantId(), entityType, pageLink);
}, 100, entity -> {
if (!importedEntities.get(entityType).contains(entity.getId())) {
try {
exportableEntitiesService.checkPermission(user, entity, entityType, Operation.DELETE);
} catch (ThingsboardException e) {
throw new RuntimeException(e);
}
exportableEntitiesService.deleteByTenantIdAndId(user.getTenantId(), entity.getId());
sendEventsCallbacks.add(() -> {
entityNotificationService.notifyDeleteEntity(user.getTenantId(), entity.getId(),
entity, null, ActionType.DELETED, null, user);
});
EntityTypeLoadResult result = results.get(entityType);
result.setDeleted(result.getDeleted() + 1);
}
});
});
versionLoadRequest.getEntityTypes().keySet().stream()
.filter(entityType -> versionLoadRequest.getEntityTypes().get(entityType).isRemoveOtherEntities())
.sorted(exportImportService.getEntityTypeComparatorForImport().reversed())
.forEach(entityType -> {
DaoUtil.processInBatches(pageLink -> {
return exportableEntitiesService.findEntitiesByTenantId(user.getTenantId(), entityType, pageLink);
}, 100, entity -> {
if (!importedEntities.get(entityType).contains(entity.getId())) {
try {
exportableEntitiesService.checkPermission(user, entity, entityType, Operation.DELETE);
} catch (ThingsboardException e) {
throw new RuntimeException(e);
}
exportableEntitiesService.deleteByTenantIdAndId(user.getTenantId(), entity.getId());
sendEventsCallbacks.add(() -> {
entityNotificationService.notifyDeleteEntity(user.getTenantId(), entity.getId(),
entity, null, ActionType.DELETED, null, user);
});
VersionLoadResult result = results.get(entityType);
result.setDeleted(result.getDeleted() + 1);
}
});
});
for (ThrowingRunnable saveReferencesCallback : saveReferencesCallbacks) {
try {
saveReferencesCallback.run();
} catch (ThingsboardException e) {
throw new RuntimeException(e);
}
}
for (ThrowingRunnable sendEventsCallback : sendEventsCallbacks) {
try {
sendEventsCallback.run();
} catch (Exception e) {
log.error("Failed to send events for entity", e);
}
}
return VersionLoadResult.success(new ArrayList<>(results.values()));
}
for (ThrowingRunnable saveReferencesCallback : saveReferencesCallbacks) {
try {
saveReferencesCallback.run();
} catch (ThingsboardException e) {
throw new RuntimeException(e);
}
}
for (ThrowingRunnable sendEventsCallback : sendEventsCallbacks) {
try {
sendEventsCallback.run();
} catch (Exception e) {
log.error("Failed to send events for entity", e);
}
}
return new ArrayList<>(results.values());
}));
private VersionLoadResult onError(EntityExportData<?> entityData, Throwable e) {
return analyze(e, entityData).orElseThrow(() -> new RuntimeException(e));
}
private Optional<VersionLoadResult> analyze(Throwable e, EntityExportData<?> entityData) {
if (e == null) {
return Optional.empty();
} else {
if (e instanceof DeviceCredentialsValidationException) {
return Optional.of(VersionLoadResult.error(EntityLoadError.credentialsError(entityData.getExternalId())));
} else if (e instanceof MissingEntityException) {
return Optional.of(VersionLoadResult.error(EntityLoadError.referenceEntityError(entityData.getExternalId(), ((MissingEntityException) e).getEntityId())));
} else {
return analyze(e.getCause(), entityData);
}
default:
throw new IllegalArgumentException("Unsupported version load request");
}
}
@ -370,7 +417,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
@Override
public ListenableFuture<EntityDataInfo> getEntityDataInfo(SecurityUser user, EntityId entityId, String versionId) {
return Futures.transform(gitServiceQueue.getEntity(user.getTenantId(), versionId, entityId),
entity -> new EntityDataInfo(entity.getRelations() != null, entity.getAttributes() != null), MoreExecutors.directExecutor());
entity -> new EntityDataInfo(entity.getRelations() != null, entity.getAttributes() != null, false), MoreExecutors.directExecutor());
}
@ -443,6 +490,35 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
return saveEntitiesVersion(user, vcr);
}
@Override
public ListenableFuture<VersionCreationResult> autoCommit(SecurityUser user, EntityType entityType, List<UUID> entityIds) throws Exception {
var repositorySettings = repositorySettingsService.get(user.getTenantId());
if (repositorySettings == null) {
return Futures.immediateFuture(null);
}
var autoCommitSettings = autoCommitSettingsService.get(user.getTenantId());
if (autoCommitSettings == null) {
return Futures.immediateFuture(null);
}
AutoVersionCreateConfig autoCommitConfig = autoCommitSettings.get(entityType);
if (autoCommitConfig == null) {
return Futures.immediateFuture(null);
}
var autoCommitBranchName = autoCommitConfig.getBranch();
if (StringUtils.isEmpty(autoCommitBranchName)) {
autoCommitBranchName = StringUtils.isNotEmpty(repositorySettings.getDefaultBranch()) ? repositorySettings.getDefaultBranch() : "auto-commits";
}
ComplexVersionCreateRequest vcr = new ComplexVersionCreateRequest();
vcr.setBranch(autoCommitBranchName);
vcr.setVersionName("auto-commit at " + Instant.ofEpochSecond(System.currentTimeMillis() / 1000));
vcr.setSyncStrategy(SyncStrategy.MERGE);
EntityTypeVersionCreateConfig vcrConfig = new EntityTypeVersionCreateConfig();
vcrConfig.setEntityIds(entityIds);
vcr.setEntityTypes(Collections.singletonMap(entityType, vcrConfig));
return saveEntitiesVersion(user, vcr);
}
private String getCauseMessage(Exception e) {
String message;
if (e.getCause() != null && StringUtils.isNotEmpty(e.getCause().getMessage())) {

2
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java

@ -108,7 +108,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
public ListenableFuture<Void> addToCommit(CommitGitRequest commit, EntityExportData<ExportableEntity<EntityId>> entityData) {
SettableFuture<Void> future = SettableFuture.create();
String path = getRelativePath(entityData.getEntityType(), entityData.getEntity().getId());
String path = getRelativePath(entityData.getEntityType(), entityData.getExternalId());
String entityDataJson = JacksonUtil.toPrettyString(entityData.sort());
registerAndSend(commit, builder -> builder.setCommitRequest(

8
application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java

@ -23,16 +23,18 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.sync.vc.EntityDataDiff;
import org.thingsboard.server.common.data.sync.vc.EntityDataInfo;
import org.thingsboard.server.common.data.sync.vc.VersionLoadResult;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
import org.thingsboard.server.common.data.sync.vc.EntityVersion;
import org.thingsboard.server.common.data.sync.vc.VersionCreationResult;
import org.thingsboard.server.common.data.sync.vc.VersionLoadResult;
import org.thingsboard.server.common.data.sync.vc.EntityTypeLoadResult;
import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo;
import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadRequest;
import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest;
import java.util.List;
import java.util.UUID;
public interface EntitiesVersionControlService {
@ -48,7 +50,7 @@ public interface EntitiesVersionControlService {
ListenableFuture<List<VersionedEntityInfo>> listAllEntitiesAtVersion(TenantId tenantId, String branch, String versionId) throws Exception;
ListenableFuture<List<VersionLoadResult>> loadEntitiesVersion(SecurityUser user, VersionLoadRequest request) throws Exception;
ListenableFuture<VersionLoadResult> loadEntitiesVersion(SecurityUser user, VersionLoadRequest request) throws Exception;
ListenableFuture<EntityDataDiff> compareEntityDataToVersion(SecurityUser user, String branch, EntityId entityId, String versionId) throws Exception;
@ -64,5 +66,7 @@ public interface EntitiesVersionControlService {
ListenableFuture<VersionCreationResult> autoCommit(SecurityUser user, EntityId entityId) throws Exception;
ListenableFuture<VersionCreationResult> autoCommit(SecurityUser user, EntityType entityType, List<UUID> entityIds) throws Exception;
ListenableFuture<EntityDataInfo> getEntityDataInfo(SecurityUser user, EntityId entityId, String versionId);
}

32
application/src/main/java/org/thingsboard/server/service/sync/vc/LoadEntityException.java

@ -0,0 +1,32 @@
/**
* 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.vc;
import lombok.Getter;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
@SuppressWarnings("rawtypes")
public class LoadEntityException extends RuntimeException {
private static final long serialVersionUID = -1749719992370409504L;
@Getter
private final EntityExportData data;
public LoadEntityException(EntityExportData data, Throwable cause) {
super(cause);
this.data = data;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.sync.ie;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
@Data
public class DeviceExportData extends EntityExportData<Device> {
@JsonProperty(index = 3)
private DeviceCredentials credentials;
}

12
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java

@ -15,8 +15,11 @@
*/
package org.thingsboard.server.common.data.sync.ie;
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;
@ -52,11 +55,15 @@ public class EntityExportData<E extends ExportableEntity<? extends EntityId>> {
public static final Comparator<AttributeExportData> attrComparator = Comparator
.comparing(AttributeExportData::getKey).thenComparing(AttributeExportData::getLastUpdateTs);
@JsonProperty(index = 2)
@JsonTbEntity
private E entity;
@JsonProperty(index = 1)
private EntityType entityType;
@JsonProperty(index = 100)
private List<EntityRelation> relations;
@JsonProperty(index = 101)
private Map<String, List<AttributeExportData>> attributes;
public EntityExportData<E> sort() {
@ -69,4 +76,9 @@ public class EntityExportData<E extends ExportableEntity<? extends EntityId>> {
return this;
}
@JsonIgnore
public EntityId getExternalId() {
return entity.getExternalId() != null ? entity.getExternalId() : entity.getId();
}
}

1
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportSettings.java

@ -27,4 +27,5 @@ import lombok.NoArgsConstructor;
public class EntityExportSettings {
private boolean exportRelations;
private boolean exportAttributes;
private boolean exportCredentials;
}

1
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportSettings.java

@ -28,6 +28,7 @@ public class EntityImportSettings {
private boolean findExistingByName;
private boolean updateRelations;
private boolean saveAttributes;
private boolean saveCredentials;
// internal
private boolean resetExternalIdsOfAnotherTenant;

2
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/RuleChainExportData.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.sync.ie;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@Data
public class RuleChainExportData extends EntityExportData<RuleChain> {
@JsonProperty(index = 3)
private RuleChainMetaData metaData;
}

1
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataInfo.java

@ -25,4 +25,5 @@ import lombok.NoArgsConstructor;
public class EntityDataInfo {
boolean hasRelations;
boolean hasAttributes;
boolean hasCredentials;
}

42
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java

@ -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.vc;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.List;
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class EntityLoadError {
private String type;
private EntityId source;
private EntityId target;
public static EntityLoadError credentialsError(EntityId sourceId) {
return EntityLoadError.builder().type("DEVICE_CREDENTIALS_CONFLICT").source(sourceId).build();
}
public static EntityLoadError referenceEntityError(EntityId sourceId, EntityId targetId) {
return EntityLoadError.builder().type("MISSING_REFERENCED_ENTITY").source(sourceId).target(targetId).build();
}
}

33
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityTypeLoadResult.java

@ -0,0 +1,33 @@
/**
* 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.vc;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.EntityType;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class EntityTypeLoadResult {
private EntityType entityType;
private int created;
private int updated;
private int deleted;
}

29
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionLoadResult.java

@ -15,19 +15,30 @@
*/
package org.thingsboard.server.common.data.sync.vc;
import lombok.AllArgsConstructor;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.EntityType;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class VersionLoadResult {
private EntityType entityType;
private int created;
private int updated;
private int deleted;
private List<EntityTypeLoadResult> result;
private EntityLoadError error;
public static VersionLoadResult success(List<EntityTypeLoadResult> result) {
return VersionLoadResult.builder().result(result).build();
}
public static VersionLoadResult success(EntityTypeLoadResult result) {
return VersionLoadResult.builder().result(List.of(result)).build();
}
public static VersionLoadResult error(EntityLoadError error) {
return VersionLoadResult.builder().error(error).build();
}
}

1
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/VersionCreateConfig.java

@ -25,4 +25,5 @@ public class VersionCreateConfig implements Serializable {
private boolean saveRelations;
private boolean saveAttributes;
private boolean saveCredentials;
}

1
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadConfig.java

@ -22,5 +22,6 @@ public class VersionLoadConfig {
private boolean loadRelations;
private boolean loadAttributes;
private boolean loadCredentials;
}

3
common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java

@ -33,7 +33,8 @@ import java.util.Arrays;
public class JacksonUtil {
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public static final ObjectMapper PRETTY_SORTED_JSON_MAPPER = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT)
public static final ObjectMapper PRETTY_SORTED_JSON_MAPPER = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
.build();

9
ui-ngx/src/app/core/http/entities-version-control.service.ts

@ -19,7 +19,7 @@ import { HttpClient } from '@angular/common/http';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { Observable, of } from 'rxjs';
import {
BranchInfo, EntityDataDiff,
BranchInfo, EntityDataDiff, EntityDataInfo,
EntityVersion,
VersionCreateRequest,
VersionCreationResult,
@ -71,6 +71,13 @@ export class EntitiesVersionControlService {
}
}
public getEntityDataInfo(externalEntityId: EntityId,
versionId: string,
config?: RequestConfig): Observable<EntityDataInfo> {
return this.http.get<EntityDataInfo>(`/api/entities/vc/info/${versionId}/${externalEntityId.entityType}/${externalEntityId.id}`,
defaultHttpOptionsFromConfig(config));
}
public saveEntitiesVersion(request: VersionCreateRequest, config?: RequestConfig): Observable<VersionCreationResult> {
return this.http.post<VersionCreationResult>('/api/entities/vc/version', request, defaultHttpOptionsFromConfig(config)).pipe(
tap(() => {

8
ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html

@ -75,11 +75,11 @@
</div>
</div>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px" formGroupName="config">
<mat-checkbox formControlName="saveRelations">
{{ 'version-control.export-entity-relations' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="saveAttributes">
{{ 'version-control.export-entity-attributes' | translate }}
{{ 'version-control.export-attributes' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="saveRelations">
{{ 'version-control.export-relations' | translate }}
</mat-checkbox>
</div>
</div>

4
ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts

@ -97,8 +97,8 @@ export class AutoCommitSettingsComponent extends PageComponent implements OnInit
const entityTypesArray = this.autoCommitSettingsForm.get('entityTypes') as FormArray;
const config: AutoVersionCreateConfig = {
branch: null,
saveRelations: false,
saveAttributes: false
saveAttributes: true,
saveRelations: false
};
const allowed = this.allowedEntityTypes();
let entityType: EntityType = null;

3
ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.html

@ -39,13 +39,14 @@
{{ 'version-control.version-name-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-form-field fxFlex class="mat-block" style="margin-bottom: 16px;">
<mat-label translate>version-control.default-sync-strategy</mat-label>
<mat-select required formControlName="syncStrategy">
<mat-option *ngFor="let strategy of syncStrategies" [value]="strategy">
{{syncStrategyTranslations.get(strategy) | translate}}
</mat-option>
</mat-select>
<mat-hint [innerHTML]="(syncStrategyHints.get(createVersionFormGroup.get('syncStrategy').value) | translate) | safe:'html'"></mat-hint>
</mat-form-field>
<tb-entity-types-version-create
formControlName="entityTypes">

4
ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.ts

@ -20,7 +20,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {
ComplexVersionCreateRequest,
createDefaultEntityTypesVersionCreate,
SyncStrategy, syncStrategyTranslationMap,
SyncStrategy, syncStrategyHintMap, syncStrategyTranslationMap,
VersionCreateRequestType,
VersionCreationResult
} from '@shared/models/vc.models';
@ -51,6 +51,8 @@ export class ComplexVersionCreateComponent extends PageComponent implements OnIn
syncStrategyTranslations = syncStrategyTranslationMap;
syncStrategyHints = syncStrategyHintMap;
resultMessage: string;
versionCreateResult: VersionCreationResult = null;

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

@ -65,11 +65,11 @@
</mat-select>
</mat-form-field>
<div fxFlex fxLayout="column" fxLayoutGap="8px">
<mat-checkbox formControlName="saveRelations">
{{ 'version-control.export-entity-relations' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="saveAttributes">
{{ 'version-control.export-entity-attributes' | translate }}
{{ 'version-control.export-attributes' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="saveRelations">
{{ 'version-control.export-relations' | translate }}
</mat-checkbox>
</div>
</div>

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

@ -183,8 +183,8 @@ export class EntityTypesVersionCreateComponent extends PageComponent implements
const entityTypesArray = this.entityTypesVersionCreateFormGroup.get('entityTypes') as FormArray;
const config: EntityTypeVersionCreateConfig = {
syncStrategy: null,
saveRelations: false,
saveAttributes: false,
saveAttributes: true,
saveRelations: true,
allEntities: true,
entityIds: []
};

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

@ -64,11 +64,11 @@
</mat-checkbox>
</div>
<div fxFlex fxLayout="column" fxLayoutGap="8px">
<mat-checkbox formControlName="loadRelations">
{{ 'version-control.load-entities-relations' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="loadAttributes">
{{ 'version-control.load-entities-attributes' | translate }}
{{ 'version-control.load-attributes' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="loadRelations">
{{ 'version-control.load-relations' | translate }}
</mat-checkbox>
</div>
</div>

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

@ -164,10 +164,10 @@ export class EntityTypesVersionLoadComponent extends PageComponent implements On
public addEntityType() {
const entityTypesArray = this.entityTypesVersionLoadFormGroup.get('entityTypes') as FormArray;
const config: EntityTypeVersionLoadConfig = {
loadRelations: false,
loadAttributes: false,
loadAttributes: true,
loadRelations: true,
removeOtherEntities: false,
findExistingEntityByName: false
findExistingEntityByName: true
};
const allowed = this.allowedEntityTypes();
let entityType: EntityType = null;

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

@ -38,11 +38,11 @@
{{ 'version-control.version-name-required' | translate }}
</mat-error>
</mat-form-field>
<mat-checkbox formControlName="saveRelations" style="margin-bottom: 16px;">
{{ 'version-control.export-entity-relations' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="saveAttributes" style="margin-bottom: 16px;">
{{ 'version-control.export-entity-attributes' | translate }}
{{ 'version-control.export-attributes' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="saveRelations" style="margin-bottom: 16px;">
{{ 'version-control.export-relations' | translate }}
</mat-checkbox>
</div>
</fieldset>
@ -57,7 +57,7 @@
<button mat-raised-button color="primary"
type="button"
(click)="export()"
[disabled]="(isLoading$ | async) || createVersionFormGroup.invalid || !createVersionFormGroup.dirty">
[disabled]="(isLoading$ | async) || createVersionFormGroup.invalid">
{{ 'action.create' | translate }}
</button>
</div>

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

@ -41,6 +41,9 @@ export class EntityVersionCreateComponent extends PageComponent implements OnIni
@Input()
entityId: EntityId;
@Input()
entityName: string;
@Input()
onClose: (result: VersionCreationResult | null, branch: string | null) => void;
@ -61,9 +64,10 @@ export class EntityVersionCreateComponent extends PageComponent implements OnIni
ngOnInit(): void {
this.createVersionFormGroup = this.fb.group({
branch: [this.branch, [Validators.required]],
versionName: [null, [Validators.required]],
versionName: [this.translate.instant('version-control.default-create-entity-version-name',
{entityName: this.entityName}), [Validators.required]],
saveRelations: [false, []],
saveAttributes: [false, []]
saveAttributes: [true, []]
});
}

3
ui-ngx/src/app/modules/home/components/vc/entity-version-diff.component.ts

@ -308,7 +308,7 @@ export class EntityVersionDiffComponent extends PageComponent implements OnInit,
this.popoverService.hidePopover(trigger);
} else {
const restoreVersionPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, EntityVersionRestoreComponent, 'left', true, null,
this.viewContainerRef, EntityVersionRestoreComponent, 'leftTop', true, null,
{
branch: this.branch,
versionName: this.versionName,
@ -322,6 +322,7 @@ export class EntityVersionDiffComponent extends PageComponent implements OnInit,
}
}
}, {}, {}, {}, false);
restoreVersionPopover.tbComponentRef.instance.popoverComponent = restoreVersionPopover;
}
}
}

19
ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.html

@ -15,27 +15,28 @@
limitations under the License.
-->
<section style="min-width: 400px;">
<mat-toolbar>
<section [ngStyle]="entityDataInfo ? {minWidth: '400px'} : {}">
<mat-toolbar *ngIf="entityDataInfo">
<h2>{{ 'version-control.restore-entity-from-version' | translate: {versionName} }}</h2>
<span fxFlex></span>
</mat-toolbar>
<mat-progress-bar color="warn" style="z-index: 10; width: 100%; margin-bottom: -4px;" mode="indeterminate"
*ngIf="isLoading$ | async">
*ngIf="entityDataInfo && (isLoading$ | async)">
</mat-progress-bar>
<form [formGroup]="restoreFormGroup" style="padding-top: 16px;">
<mat-spinner *ngIf="!entityDataInfo" mode="indeterminate" diameter="80"></mat-spinner>
<form *ngIf="entityDataInfo" [formGroup]="restoreFormGroup" style="padding-top: 16px;">
<fieldset [disabled]="isLoading$ | async">
<div fxFlex fxLayout="column">
<mat-checkbox formControlName="loadRelations" style="margin-bottom: 16px;">
{{ 'version-control.load-entity-relations' | translate }}
<mat-checkbox *ngIf="entityDataInfo.hasAttributes" formControlName="loadAttributes" style="margin-bottom: 16px;">
{{ 'version-control.load-attributes' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="loadAttributes" style="margin-bottom: 16px;">
{{ 'version-control.load-entity-attributes' | translate }}
<mat-checkbox *ngIf="entityDataInfo.hasRelations" formControlName="loadRelations" style="margin-bottom: 16px;">
{{ 'version-control.load-relations' | translate }}
</mat-checkbox>
</div>
</fieldset>
</form>
<div fxLayoutAlign="end center" fxLayoutGap="8px">
<div *ngIf="entityDataInfo" fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"

32
ui-ngx/src/app/modules/home/components/vc/entity-version-restore.component.ts

@ -14,15 +14,22 @@
/// limitations under the License.
///
import { Component, Input, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { FormBuilder, FormGroup } from '@angular/forms';
import { SingleEntityVersionLoadRequest, VersionLoadRequestType, VersionLoadResult } from '@shared/models/vc.models';
import {
EntityDataInfo,
SingleEntityVersionLoadRequest,
VersionLoadRequestType,
VersionLoadResult
} from '@shared/models/vc.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntitiesVersionControlService } from '@core/http/entities-version-control.service';
import { EntityId } from '@shared/models/id/entity-id';
import { TranslateService } from '@ngx-translate/core';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { delay } from 'rxjs/operators';
@Component({
selector: 'tb-entity-version-restore',
@ -46,10 +53,16 @@ export class EntityVersionRestoreComponent extends PageComponent implements OnIn
@Input()
onClose: (result: Array<VersionLoadResult> | null) => void;
@Input()
popoverComponent: TbPopoverComponent;
entityDataInfo: EntityDataInfo = null;
restoreFormGroup: FormGroup;
constructor(protected store: Store<AppState>,
private entitiesVersionControlService: EntitiesVersionControlService,
private cd: ChangeDetectorRef,
private translate: TranslateService,
private fb: FormBuilder) {
super(store);
@ -57,8 +70,15 @@ export class EntityVersionRestoreComponent extends PageComponent implements OnIn
ngOnInit(): void {
this.restoreFormGroup = this.fb.group({
loadRelations: [false, []],
loadAttributes: [false, []]
loadAttributes: [true, []],
loadRelations: [true, []]
});
this.entitiesVersionControlService.getEntityDataInfo(this.externalEntityId, this.versionId).subscribe((data) => {
this.entityDataInfo = data;
this.cd.detectChanges();
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
});
}
@ -74,8 +94,8 @@ export class EntityVersionRestoreComponent extends PageComponent implements OnIn
versionId: this.versionId,
externalEntityId: this.externalEntityId,
config: {
loadRelations: this.restoreFormGroup.get('loadRelations').value,
loadAttributes: this.restoreFormGroup.get('loadAttributes').value
loadRelations: this.entityDataInfo.hasRelations ? this.restoreFormGroup.get('loadRelations').value : false,
loadAttributes: this.entityDataInfo.hasAttributes ? this.restoreFormGroup.get('loadAttributes').value : false
},
type: VersionLoadRequestType.SINGLE_ENTITY
};

5
ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts

@ -105,6 +105,9 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
@Input()
entityId: EntityId;
@Input()
entityName: string;
@Output()
versionRestored = new EventEmitter<void>();
@ -191,6 +194,7 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
{
branch: this.branch,
entityId: this.entityId,
entityName: this.entityName,
onClose: (result: VersionCreationResult | null, branch: string | null) => {
createVersionPopover.hide();
if (result) {
@ -289,6 +293,7 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
}
}
}, {}, {}, {}, false);
restoreVersionPopover.tbComponentRef.instance.popoverComponent = restoreVersionPopover;
}
}

1
ui-ngx/src/app/modules/home/components/vc/version-control.component.html

@ -22,6 +22,7 @@
<tb-entity-versions-table [singleEntityMode]="singleEntityMode"
[active]="active"
[entityId]="entityId"
[entityName]="entityName"
[externalEntityId]="externalEntityId"
(versionRestored)="versionRestored.emit()"></tb-entity-versions-table>
</ng-template>

3
ui-ngx/src/app/modules/home/components/vc/version-control.component.ts

@ -47,6 +47,9 @@ export class VersionControlComponent implements OnInit, HasConfirmForm {
@Input()
entityId: EntityId;
@Input()
entityName: string;
@Output()
versionRestored = new EventEmitter<void>();

2
ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html

@ -53,5 +53,5 @@
label="{{ 'version-control.version-control' | translate }}" #versionControlTab="matTab">
<tb-version-control detailsMode="true" singleEntityMode="true"
(versionRestored)="entitiesTableConfig.updateData()"
[active]="versionControlTab.isActive" [entityId]="entity.id" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
[active]="versionControlTab.isActive" [entityId]="entity.id" [entityName]="entity.name" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
</mat-tab>

2
ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html

@ -53,5 +53,5 @@
label="{{ 'version-control.version-control' | translate }}" #versionControlTab="matTab">
<tb-version-control detailsMode="true" singleEntityMode="true"
(versionRestored)="entitiesTableConfig.updateData()"
[active]="versionControlTab.isActive" [entityId]="entity.id" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
[active]="versionControlTab.isActive" [entityId]="entity.id" [entityName]="entity.name" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
</mat-tab>

2
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-tabs.component.html

@ -23,5 +23,5 @@
label="{{ 'version-control.version-control' | translate }}" #versionControlTab="matTab">
<tb-version-control detailsMode="true" singleEntityMode="true"
(versionRestored)="entitiesTableConfig.updateData()"
[active]="versionControlTab.isActive" [entityId]="entity.id" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
[active]="versionControlTab.isActive" [entityId]="entity.id" [entityName]="entity.name" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
</mat-tab>

2
ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html

@ -79,5 +79,5 @@
label="{{ 'version-control.version-control' | translate }}" #versionControlTab="matTab">
<tb-version-control detailsMode="true" singleEntityMode="true"
(versionRestored)="entitiesTableConfig.updateData()"
[active]="versionControlTab.isActive" [entityId]="entity.id" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
[active]="versionControlTab.isActive" [entityId]="entity.id" [entityName]="entity.name" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
</mat-tab>

2
ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html

@ -53,5 +53,5 @@
label="{{ 'version-control.version-control' | translate }}" #versionControlTab="matTab">
<tb-version-control detailsMode="true" singleEntityMode="true"
(versionRestored)="entitiesTableConfig.updateData()"
[active]="versionControlTab.isActive" [entityId]="entity.id" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
[active]="versionControlTab.isActive" [entityId]="entity.id" [entityName]="entity.name" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
</mat-tab>

2
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html

@ -56,5 +56,5 @@
label="{{ 'version-control.version-control' | translate }}" #versionControlTab="matTab">
<tb-version-control detailsMode="true" singleEntityMode="true"
(versionRestored)="entitiesTableConfig.updateData()"
[active]="versionControlTab.isActive" [entityId]="entity.id" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
[active]="versionControlTab.isActive" [entityId]="entity.id" [entityName]="entity.name" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
</mat-tab>

12
ui-ngx/src/app/shared/components/popover.component.ts

@ -55,7 +55,7 @@ import {
POSITION_MAP,
PropertyMapping
} from '@shared/components/popover.models';
import { distinctUntilChanged, takeUntil } from 'rxjs/operators';
import { distinctUntilChanged, take, takeUntil } from 'rxjs/operators';
import { isNotEmptyStr, onParentScrollOrWindowResize } from '@core/utils';
export type TbPopoverTrigger = 'click' | 'focus' | 'hover' | null;
@ -372,7 +372,7 @@ export class TbPopoverComponent implements OnDestroy, OnInit {
}
get tbVisible(): boolean {
return this.visible;
return this.visible && this.tbAnimationState === 'active';
}
visible = false;
@ -514,10 +514,12 @@ export class TbPopoverComponent implements OnDestroy, OnInit {
const el = this.origin.elementRef.nativeElement;
this.intersectionObserver.unobserve(el);
}
this.tbVisible = false;
this.tbVisibleChange.next(false);
this.tbAnimationState = 'void';
this.cdr.detectChanges();
this.tbAnimationDone.pipe(take(1)).subscribe(() => {
this.tbVisible = false;
this.cdr.detectChanges();
});
}
updateByDirective(): void {

8
ui-ngx/src/app/shared/components/popover.service.ts

@ -80,9 +80,7 @@ export class TbPopoverService {
component.tbShowCloseButton = showCloseButton;
component.tbVisibleChange.subscribe((visible: boolean) => {
if (!visible) {
component.tbAnimationDone.subscribe(() => {
componentRef.destroy();
});
componentRef.destroy();
}
});
component.tbDestroy.subscribe(() => {
@ -132,9 +130,7 @@ export class TbPopoverService {
component.tbVisibleChange.subscribe((visible: boolean) => {
if (!visible) {
visibleFn(false);
component.tbAnimationDone.subscribe(() => {
componentRef.destroy();
});
componentRef.destroy();
}
});
component.tbDestroy.subscribe(() => {

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

@ -64,6 +64,13 @@ export const syncStrategyTranslationMap = new Map<SyncStrategy, string>(
]
);
export const syncStrategyHintMap = new Map<SyncStrategy, string>(
[
[SyncStrategy.MERGE, 'version-control.sync-strategy-merge-hint'],
[SyncStrategy.OVERWRITE, 'version-control.sync-strategy-overwrite-hint']
]
);
export interface EntityTypeVersionCreateConfig extends VersionCreateConfig {
syncStrategy: SyncStrategy;
entityIds: string[];
@ -81,8 +88,8 @@ export function createDefaultEntityTypesVersionCreate(): {[entityType: string]:
for (const entityType of exportableEntityTypes) {
res[entityType] = {
syncStrategy: null,
saveRelations: false,
saveAttributes: false,
saveAttributes: true,
saveRelations: true,
allEntities: true,
entityIds: []
};
@ -126,10 +133,10 @@ export function createDefaultEntityTypesVersionLoad(): {[entityType: string]: En
const res: {[entityType: string]: EntityTypeVersionLoadConfig} = {};
for (const entityType of exportableEntityTypes) {
res[entityType] = {
loadRelations: false,
loadAttributes: false,
loadAttributes: true,
loadRelations: true,
removeOtherEntities: false,
findExistingEntityByName: false
findExistingEntityByName: true
};
}
return res;
@ -184,3 +191,8 @@ export interface EntityDataDiff {
export function entityExportDataToJsonString(data: EntityExportData<any>): string {
return JSON.stringify(data, null, 4);
}
export interface EntityDataInfo {
hasRelations: boolean;
hasAttributes: boolean;
}

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

@ -3129,8 +3129,8 @@
"version-name": "Version name",
"version-name-required": "Version name is required",
"author": "Author",
"export-entity-relations": "Export entity relations",
"export-entity-attributes": "Export entity attributes",
"export-relations": "Export relations",
"export-attributes": "Export attributes",
"entity-versions": "Entity versions",
"versions": "Versions",
"created-time": "Created time",
@ -3142,8 +3142,8 @@
"nothing-to-commit": "No changes to commit",
"restore-version": "Restore version",
"restore-entity-from-version": "Restore entity from version '{{versionName}}'",
"load-entity-relations": "Load entity relations",
"load-entity-attributes": "Load entity attributes",
"load-relations": "Load relations",
"load-attributes": "Load attributes",
"show-version-diff": "Show version diff",
"diff-entity-with-version": "Diff with entity version '{{versionName}}'",
"previous-difference": "Previous Difference",
@ -3163,8 +3163,6 @@
"add-entity-type": "Add entity type",
"remove-all": "Remove all",
"version-create-result": "{ added, plural, 0 {No entities} 1 {1 entity} other {# entities} } added.\n{ modified, plural, 0 {No entities} 1 {1 entity} other {# entities} } modified.\n{ removed, plural, 0 {No entities} 1 {1 entity} other {# entities} } removed.",
"load-entities-relations": "Load entities relations",
"load-entities-attributes": "Load entities attributes",
"remove-other-entities": "Remove other entities",
"find-existing-entity-by-name": "Find existing entity by name",
"restore-entities-from-version": "Restore entities from version '{{versionName}}'",
@ -3173,7 +3171,10 @@
"updated": "{{updated}} updated",
"deleted": "{{deleted}} deleted",
"remove-other-entities-confirm-text": "Be careful! This will permanently <b>delete</b> <b>all</b> current entities<br/>not present in the version you want to restore.<br/><br/>Please type <b>remove other entities</b> to confirm.",
"auto-commit-to-branch": "auto-commit to <b>{{ branch }}</b> branch"
"auto-commit-to-branch": "auto-commit to <b>{{ branch }}</b> branch",
"default-create-entity-version-name": "{{entityName}} update",
"sync-strategy-merge-hint": "Sync strategy <b>merge</b> hint",
"sync-strategy-overwrite-hint": "Sync strategy <b>overwrite</b> hint"
},
"widget": {
"widget-library": "Widgets Library",

Loading…
Cancel
Save