diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/exports/ActionCollectionExportableServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/exports/ActionCollectionExportableServiceCEImpl.java index 408b6de22a..a6c266282f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/exports/ActionCollectionExportableServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/actioncollections/exports/ActionCollectionExportableServiceCEImpl.java @@ -49,7 +49,7 @@ public class ActionCollectionExportableServiceCEImpl implements ExportableServic Optional optionalPermission = Optional.ofNullable(actionPermission.getExportPermission( exportingMetaDTO.getIsGitSync(), exportingMetaDTO.getExportWithConfiguration())); Flux actionCollectionFlux = actionCollectionService.findByPageIdsForExport( - exportingMetaDTO.getUnpublishedPages(), optionalPermission); + exportingMetaDTO.getUnpublishedModulesOrPages(), optionalPermission); return actionCollectionFlux .collectList() .map(actionCollectionList -> { @@ -80,11 +80,9 @@ public class ActionCollectionExportableServiceCEImpl implements ExportableServic boolean isActionCollectionUpdated = exportingMetaDTO.isClientSchemaMigrated() || exportingMetaDTO.isServerSchemaMigrated() || isPageUpdated - || exportingMetaDTO.getApplicationLastCommittedAt() == null + || exportingMetaDTO.getArtifactLastCommittedAt() == null || actionCollectionUpdatedAt == null - || exportingMetaDTO - .getApplicationLastCommittedAt() - .isBefore(actionCollectionUpdatedAt); + || exportingMetaDTO.getArtifactLastCommittedAt().isBefore(actionCollectionUpdatedAt); if (isActionCollectionUpdated) { updatedActionCollectionSet.add(actionCollectionName); } @@ -117,8 +115,9 @@ public class ActionCollectionExportableServiceCEImpl implements ExportableServic // be used to replace collectionIds in action if (actionCollection.getUnpublishedCollection() != null) { ActionCollectionDTO actionCollectionDTO = actionCollection.getUnpublishedCollection(); - actionCollectionDTO.setPageId( - mappedExportableResourcesDTO.getPageIdToNameMap().get(actionCollectionDTO.getPageId() + EDIT)); + actionCollectionDTO.setPageId(mappedExportableResourcesDTO + .getPageOrModuleIdToNameMap() + .get(actionCollectionDTO.getPageId() + EDIT)); actionCollectionDTO.setPluginId( mappedExportableResourcesDTO.getPluginMap().get(actionCollectionDTO.getPluginId())); @@ -131,8 +130,9 @@ public class ActionCollectionExportableServiceCEImpl implements ExportableServic } if (actionCollection.getPublishedCollection() != null) { ActionCollectionDTO actionCollectionDTO = actionCollection.getPublishedCollection(); - actionCollectionDTO.setPageId( - mappedExportableResourcesDTO.getPageIdToNameMap().get(actionCollectionDTO.getPageId() + VIEW)); + actionCollectionDTO.setPageId(mappedExportableResourcesDTO + .getPageOrModuleIdToNameMap() + .get(actionCollectionDTO.getPageId() + VIEW)); actionCollectionDTO.setPluginId( mappedExportableResourcesDTO.getPluginMap().get(actionCollectionDTO.getPluginId())); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportService.java new file mode 100644 index 0000000000..bbfb85dbd9 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportService.java @@ -0,0 +1,3 @@ +package com.appsmith.server.applications.exports; + +public interface ApplicationExportService extends ApplicationExportServiceCE {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportServiceCE.java new file mode 100644 index 0000000000..25724fa284 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportServiceCE.java @@ -0,0 +1,7 @@ +package com.appsmith.server.applications.exports; + +import com.appsmith.server.domains.Application; +import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.exports.internal.ContextBasedExportService; + +public interface ApplicationExportServiceCE extends ContextBasedExportService {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportServiceCEImpl.java new file mode 100644 index 0000000000..f78d8964a3 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportServiceCEImpl.java @@ -0,0 +1,199 @@ +package com.appsmith.server.applications.exports; + +import com.appsmith.server.acl.AclPermission; +import com.appsmith.server.applications.base.ApplicationService; +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.constants.SerialiseArtifactObjective; +import com.appsmith.server.domains.ActionCollection; +import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ApplicationPage; +import com.appsmith.server.domains.ExportableArtifact; +import com.appsmith.server.domains.GitApplicationMetadata; +import com.appsmith.server.domains.NewAction; +import com.appsmith.server.domains.NewPage; +import com.appsmith.server.domains.Theme; +import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.ArtifactExchangeJson; +import com.appsmith.server.dtos.ExportingMetaDTO; +import com.appsmith.server.dtos.MappedExportableResourcesDTO; +import com.appsmith.server.exceptions.AppsmithError; +import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.exports.exportable.ExportableService; +import com.appsmith.server.migrations.JsonSchemaVersions; +import com.appsmith.server.solutions.ApplicationPermission; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import static java.lang.Boolean.TRUE; + +@Slf4j +public class ApplicationExportServiceCEImpl implements ApplicationExportServiceCE { + + private final ApplicationService applicationService; + private final ApplicationPermission applicationPermission; + private final ExportableService newPageExportableService; + protected final ExportableService newActionExportableService; + protected final ExportableService actionCollectionExportableService; + private final ExportableService themeExportableService; + private final Map applicationConstantsMap = new HashMap<>(); + + public ApplicationExportServiceCEImpl( + ApplicationService applicationService, + ApplicationPermission applicationPermission, + ExportableService newPageExportableService, + ExportableService newActionExportableService, + ExportableService actionCollectionExportableService, + ExportableService themeExportableService) { + this.applicationService = applicationService; + this.newPageExportableService = newPageExportableService; + this.newActionExportableService = newActionExportableService; + this.actionCollectionExportableService = actionCollectionExportableService; + this.themeExportableService = themeExportableService; + this.applicationPermission = applicationPermission; + applicationConstantsMap.putAll( + Map.of(FieldName.ARTIFACT_CONTEXT, FieldName.APPLICATION, FieldName.ID, FieldName.APPLICATION_ID)); + } + + @Override + public ApplicationJson createNewArtifactExchangeJson() { + return new ApplicationJson(); + } + + @Override + public AclPermission getArtifactExportPermission(Boolean isGitSync, Boolean exportWithConfiguration) { + return applicationPermission.getExportPermission(isGitSync, exportWithConfiguration); + } + + @Override + public Mono findExistingArtifactByIdAndBranchName( + String artifactId, String branchName, AclPermission aclPermission) { + // find the application with appropriate permission + return applicationService + .findByBranchNameAndDefaultApplicationId(branchName, artifactId, aclPermission) + // Find the application without permissions if it is a template application + .switchIfEmpty( + Mono.defer(() -> applicationService.findByIdAndExportWithConfiguration(artifactId, TRUE))) + .switchIfEmpty(Mono.error( + new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION_ID, artifactId))); + } + + @Override + public Mono findExistingArtifactForAnalytics(String artifactId) { + return applicationService.findById(artifactId); + } + + @Override + public Map getExportRelatedArtifactData(ArtifactExchangeJson artifactExchangeJson) { + + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + return Map.of( + "pageCount", + applicationJson.getPageList().size(), + "actionCount", + applicationJson.getActionList().size(), + "JSObjectCount", + applicationJson.getActionCollectionList().size()); + } + + @Override + public void getArtifactReadyForExport( + ExportableArtifact exportableArtifact, + ArtifactExchangeJson artifactExchangeJson, + ExportingMetaDTO exportingMetaDTO) { + + Application application = (Application) exportableArtifact; + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + + GitApplicationMetadata gitApplicationMetadata = application.getGitApplicationMetadata(); + Instant applicationLastCommittedAt = + gitApplicationMetadata != null ? gitApplicationMetadata.getLastCommittedAt() : null; + boolean isClientSchemaMigrated = !JsonSchemaVersions.clientVersion.equals(application.getClientSchemaVersion()); + boolean isServerSchemaMigrated = !JsonSchemaVersions.serverVersion.equals(application.getServerSchemaVersion()); + + exportingMetaDTO.setArtifactLastCommittedAt(applicationLastCommittedAt); + exportingMetaDTO.setClientSchemaMigrated(isClientSchemaMigrated); + exportingMetaDTO.setServerSchemaMigrated(isServerSchemaMigrated); + applicationJson.setExportedApplication(application); + applicationJson.setUpdatedResources(new ConcurrentHashMap<>()); + + List unpublishedPages = + application.getPages().stream().map(ApplicationPage::getId).collect(Collectors.toList()); + + exportingMetaDTO.setUnpublishedModulesOrPages(unpublishedPages); + } + + @Override + public Map getConstantsMap() { + return applicationConstantsMap; + } + + @Override + public void sanitizeArtifactSpecificExportableEntities( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedExportableResourcesDTO, + ArtifactExchangeJson artifactExchangeJson, + SerialiseArtifactObjective serialiseArtifactObjective) { + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + newPageExportableService.sanitizeEntities( + exportingMetaDTO, mappedExportableResourcesDTO, applicationJson, serialiseArtifactObjective); + } + + @Override + public Flux generateArtifactSpecificExportables( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedResourcesDTO, + Mono exportableArtifactMono, + ArtifactExchangeJson artifactExchangeJson) { + return exportableArtifactMono.flatMapMany(exportableArtifact -> { + Mono applicationMono = Mono.just((Application) exportableArtifact); + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + + // Directly updates required theme information in application json + Mono themeExportablesMono = themeExportableService.getExportableEntities( + exportingMetaDTO, mappedResourcesDTO, applicationMono, applicationJson); + + // Updates pageId to name map in exportable resources. + // Also directly updates required pages information in application json + Mono newPageExportablesMono = newPageExportableService.getExportableEntities( + exportingMetaDTO, mappedResourcesDTO, applicationMono, applicationJson); + + return Flux.merge(newPageExportablesMono, themeExportablesMono); + }); + } + + @Override + public Flux generateArtifactComponentDependentExportables( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedResourcesDTO, + Mono exportableArtifactMono, + ArtifactExchangeJson artifactExchangeJson) { + return exportableArtifactMono.flatMapMany(exportableArtifact -> { + Mono applicationMono = Mono.just((Application) exportableArtifact); + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + + // Requires pageIdToNameMap, pluginMap. + // Updates collectionId to name map in exportable resources. + // Also directly updates required collection information in application json + Mono actionCollectionExportablesMono = actionCollectionExportableService.getExportableEntities( + exportingMetaDTO, mappedResourcesDTO, applicationMono, applicationJson); + + // Requires datasourceIdToNameMap, pageIdToNameMap, pluginMap, collectionIdToNameMap + // Updates actionId to name map in exportable resources. + // Also directly updates required collection information in application json + Mono newActionExportablesMono = newActionExportableService.getExportableEntities( + exportingMetaDTO, mappedResourcesDTO, applicationMono, applicationJson); + + Mono combinedActionExportablesMono = actionCollectionExportablesMono.then(newActionExportablesMono); + + return combinedActionExportablesMono.flux(); + }); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportServiceImpl.java new file mode 100644 index 0000000000..e7f5e066ab --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/exports/ApplicationExportServiceImpl.java @@ -0,0 +1,32 @@ +package com.appsmith.server.applications.exports; + +import com.appsmith.server.applications.base.ApplicationService; +import com.appsmith.server.domains.ActionCollection; +import com.appsmith.server.domains.NewAction; +import com.appsmith.server.domains.NewPage; +import com.appsmith.server.domains.Theme; +import com.appsmith.server.exports.exportable.ExportableService; +import com.appsmith.server.solutions.ApplicationPermission; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class ApplicationExportServiceImpl extends ApplicationExportServiceCEImpl implements ApplicationExportService { + + public ApplicationExportServiceImpl( + ApplicationService applicationService, + ApplicationPermission applicationPermission, + ExportableService newPageExportableService, + ExportableService newActionExportableService, + ExportableService actionCollectionExportableService, + ExportableService themeExportableService) { + super( + applicationService, + applicationPermission, + newPageExportableService, + newActionExportableService, + actionCollectionExportableService, + themeExportableService); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/SerialiseApplicationObjective.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/SerialiseArtifactObjective.java similarity index 79% rename from app/server/appsmith-server/src/main/java/com/appsmith/server/constants/SerialiseApplicationObjective.java rename to app/server/appsmith-server/src/main/java/com/appsmith/server/constants/SerialiseArtifactObjective.java index d546144bf5..01e4a9c670 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/SerialiseApplicationObjective.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/SerialiseArtifactObjective.java @@ -1,6 +1,6 @@ package com.appsmith.server.constants; -public enum SerialiseApplicationObjective { +public enum SerialiseArtifactObjective { // For which purpose we are serialising the application from DB VERSION_CONTROL, SHARE, diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java index 495c0624f4..b839f7a870 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java @@ -3,6 +3,7 @@ package com.appsmith.server.controllers; import com.appsmith.server.applications.base.ApplicationService; import com.appsmith.server.constants.Url; import com.appsmith.server.controllers.ce.ApplicationControllerCE; +import com.appsmith.server.exports.exportable.ExportService; import com.appsmith.server.exports.internal.ExportApplicationService; import com.appsmith.server.exports.internal.PartialExportService; import com.appsmith.server.fork.internal.ApplicationForkingService; @@ -31,7 +32,8 @@ public class ApplicationController extends ApplicationControllerCE { ApplicationSnapshotService applicationSnapshotService, PartialExportService partialExportService, PartialImportService partialImportService, - ImportService importService) { + ImportService importService, + ExportService exportService) { super( service, applicationPageService, @@ -43,6 +45,7 @@ public class ApplicationController extends ApplicationControllerCE { applicationSnapshotService, partialExportService, partialImportService, - importService); + importService, + exportService); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java index ef798c9228..0a79cebda4 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java @@ -21,6 +21,7 @@ import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.dtos.UserHomepageDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.exports.exportable.ExportService; import com.appsmith.server.exports.internal.ExportApplicationService; import com.appsmith.server.exports.internal.PartialExportService; import com.appsmith.server.fork.internal.ApplicationForkingService; @@ -72,6 +73,7 @@ public class ApplicationControllerCE extends BaseController { HttpHeaders responseHeaders = fetchedResource.getHttpHeaders(); - Object applicationResource = fetchedResource.getApplicationResource(); + Object applicationResource = fetchedResource.getArtifactResource(); return new ResponseEntity<>(applicationResource, responseHeaders, HttpStatus.OK); }); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/exports/DatasourceExportableServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/exports/DatasourceExportableServiceCEImpl.java index 432a17a5dc..4c645ac06a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/exports/DatasourceExportableServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/exports/DatasourceExportableServiceCEImpl.java @@ -9,11 +9,13 @@ import com.appsmith.external.models.DatasourceStorage; import com.appsmith.external.models.DecryptedSensitiveFields; import com.appsmith.external.models.OAuth2; import com.appsmith.server.acl.AclPermission; -import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.constants.SerialiseArtifactObjective; import com.appsmith.server.datasources.base.DatasourceService; import com.appsmith.server.datasourcestorages.base.DatasourceStorageService; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ExportableArtifact; import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.ArtifactExchangeJson; import com.appsmith.server.dtos.ExportingMetaDTO; import com.appsmith.server.dtos.MappedExportableResourcesDTO; import com.appsmith.server.exports.exportable.ExportableServiceCE; @@ -102,6 +104,21 @@ public class DatasourceExportableServiceCEImpl implements ExportableServiceCE getExportableEntities( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedExportableResourcesDTO, + Mono exportableArtifactMono, + ArtifactExchangeJson artifactExchangeJson, + Boolean isContextAgnostic) { + return exportableArtifactMono.flatMap(exportableArtifact -> { + Mono applicationMono = Mono.just((Application) exportableArtifact); + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + return getExportableEntities( + exportingMetaDTO, mappedExportableResourcesDTO, applicationMono, applicationJson); + }); + } + private void removeSensitiveFields(DatasourceStorage datasourceStorage) { if (datasourceStorage.getDatasourceConfiguration() != null) { datasourceStorage.getDatasourceConfiguration().setAuthentication(null); @@ -123,16 +140,27 @@ public class DatasourceExportableServiceCEImpl implements ExportableServiceCE(); } + @Override + public void sanitizeEntities( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedExportableResourcesDTO, + ArtifactExchangeJson artifactExchangeJson, + SerialiseArtifactObjective serialiseFor, + Boolean isContextAgnositc) { + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + sanitizeEntities(exportingMetaDTO, mappedExportableResourcesDTO, applicationJson, serialiseFor); + } + @Override public void sanitizeEntities( ExportingMetaDTO exportingMetaDTO, MappedExportableResourcesDTO mappedExportableResourcesDTO, ApplicationJson applicationJson, - SerialiseApplicationObjective serialiseFor) { + SerialiseArtifactObjective serialiseFor) { // Save decrypted fields for datasources for internally used sample apps and templates // only when serialising for file sharing if (TRUE.equals(exportingMetaDTO.getExportWithConfiguration()) - && SerialiseApplicationObjective.SHARE.equals(serialiseFor)) { + && SerialiseArtifactObjective.SHARE.equals(serialiseFor)) { // Save decrypted fields for datasources Map decryptedFields = new HashMap<>(); applicationJson.getDatasourceList().forEach(datasourceStorage -> { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java index 303c4ff7a2..170fb0225b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java @@ -34,7 +34,7 @@ import static com.appsmith.server.helpers.DateUtils.ISO_FORMATTER; @NoArgsConstructor @QueryEntity @Document -public class Application extends BaseDomain implements ImportableArtifact { +public class Application extends BaseDomain implements ImportableArtifact, ExportableArtifact { @NotNull @JsonView(Views.Public.class) String name; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ExportableArtifact.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ExportableArtifact.java new file mode 100644 index 0000000000..ffb27219f7 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ExportableArtifact.java @@ -0,0 +1,5 @@ +package com.appsmith.server.domains; + +import com.appsmith.server.domains.ce.ExportableArtifactCE; + +public interface ExportableArtifact extends ExportableArtifactCE {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/ExportableArtifactCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/ExportableArtifactCE.java new file mode 100644 index 0000000000..9660053901 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/ExportableArtifactCE.java @@ -0,0 +1,18 @@ +package com.appsmith.server.domains.ce; + +public interface ExportableArtifactCE { + + String getId(); + + String getName(); + + String getWorkspaceId(); + + Boolean getExportWithConfiguration(); + + void setExportWithConfiguration(Boolean bool); + + void makePristine(); + + void sanitiseToExportDBObject(); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ExportFileDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ExportFileDTO.java index 49b1129a06..6ee84e48e0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ExportFileDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ExportFileDTO.java @@ -6,5 +6,5 @@ import org.springframework.http.HttpHeaders; @Data public class ExportFileDTO { HttpHeaders httpHeaders; - Object applicationResource; + Object artifactResource; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ExportingMetaDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ExportingMetaDTO.java index 50b390048d..8d5446d506 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ExportingMetaDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ExportingMetaDTO.java @@ -13,14 +13,14 @@ import java.util.List; @NoArgsConstructor @Builder(toBuilder = true) public class ExportingMetaDTO { - String applicationId; + String artifactId; String branchName; Boolean isGitSync; Boolean exportWithConfiguration; - Instant applicationLastCommittedAt; + Instant artifactLastCommittedAt; boolean isClientSchemaMigrated; boolean isServerSchemaMigrated; - List unpublishedPages; + List unpublishedModulesOrPages; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ApplicationJsonCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ApplicationJsonCE.java index 81d380131d..19f2d345cc 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ApplicationJsonCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ApplicationJsonCE.java @@ -9,6 +9,7 @@ import com.appsmith.server.constants.ArtifactJsonType; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.ExportableArtifact; import com.appsmith.server.domains.ImportableArtifact; import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; @@ -125,6 +126,11 @@ public class ApplicationJsonCE implements ArtifactExchangeJson { return this.getExportedApplication(); } + @Override + public ExportableArtifact getExportableArtifact() { + return this.getExportedApplication(); + } + @Override public List getCustomJsLibFromArtifact() { return this.getCustomJSLibList(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ArtifactExchangeJsonCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ArtifactExchangeJsonCE.java index 520af3f880..def689c965 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ArtifactExchangeJsonCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/ArtifactExchangeJsonCE.java @@ -2,6 +2,7 @@ package com.appsmith.server.dtos.ce; import com.appsmith.server.constants.ArtifactJsonType; import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.ExportableArtifact; import com.appsmith.server.domains.ImportableArtifact; import java.util.List; @@ -20,5 +21,7 @@ public interface ArtifactExchangeJsonCE { ImportableArtifact getImportableArtifact(); + ExportableArtifact getExportableArtifact(); + List getCustomJsLibFromArtifact(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/MappedExportableResourcesCE_DTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/MappedExportableResourcesCE_DTO.java index f34f03df03..3bc910fed0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/MappedExportableResourcesCE_DTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/MappedExportableResourcesCE_DTO.java @@ -14,7 +14,7 @@ public class MappedExportableResourcesCE_DTO { Map pluginMap = new HashMap<>(); Map datasourceIdToNameMap = new HashMap<>(); Map datasourceNameToUpdatedAtMap = new HashMap<>(); - Map pageIdToNameMap = new HashMap<>(); + Map pageOrModuleIdToNameMap = new HashMap<>(); Map actionIdToNameMap = new HashMap<>(); Map collectionIdToNameMap = new HashMap<>(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportService.java new file mode 100644 index 0000000000..225fe0b24f --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportService.java @@ -0,0 +1,3 @@ +package com.appsmith.server.exports.exportable; + +public interface ExportService extends ExportServiceCE {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportServiceCE.java new file mode 100644 index 0000000000..998543ce5d --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportServiceCE.java @@ -0,0 +1,33 @@ +package com.appsmith.server.exports.exportable; + +import com.appsmith.server.constants.ArtifactJsonType; +import com.appsmith.server.constants.SerialiseArtifactObjective; +import com.appsmith.server.dtos.ArtifactExchangeJson; +import com.appsmith.server.dtos.ExportFileDTO; +import com.appsmith.server.exports.internal.ContextBasedExportService; +import reactor.core.publisher.Mono; + +public interface ExportServiceCE { + + ContextBasedExportService getContextBasedExportService(ArtifactJsonType artifactJsonType); + + Mono exportByExportableArtifactIdAndBranchName( + String artifactId, + String branchName, + SerialiseArtifactObjective objective, + ArtifactJsonType artifactJsonType); + + /** + * This function will give the artifact the resources to rebuild the artifact in import artifact flow + * + * @param artifactId which needs to be exported + * @return application reference from which entire application can be rehydrated + */ + Mono exportByArtifactId( + String artifactId, SerialiseArtifactObjective objective, ArtifactJsonType artifactJsonType); + + Mono exportByArtifactIdAndBranchName( + String artifactId, String branchName, ArtifactJsonType artifactJsonType); + + Mono getArtifactFile(String artifactId, String branchName, ArtifactJsonType artifactJsonType); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportServiceCEImpl.java new file mode 100644 index 0000000000..f9bd5fec09 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportServiceCEImpl.java @@ -0,0 +1,337 @@ +package com.appsmith.server.exports.exportable; + +import com.appsmith.external.constants.AnalyticsEvents; +import com.appsmith.external.helpers.Stopwatch; +import com.appsmith.external.models.Datasource; +import com.appsmith.server.acl.AclPermission; +import com.appsmith.server.applications.exports.ApplicationExportService; +import com.appsmith.server.constants.ArtifactJsonType; +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.constants.SerialiseArtifactObjective; +import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.ExportableArtifact; +import com.appsmith.server.domains.Plugin; +import com.appsmith.server.dtos.ArtifactExchangeJson; +import com.appsmith.server.dtos.ExportFileDTO; +import com.appsmith.server.dtos.ExportingMetaDTO; +import com.appsmith.server.dtos.MappedExportableResourcesDTO; +import com.appsmith.server.exceptions.AppsmithError; +import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.exports.internal.ContextBasedExportService; +import com.appsmith.server.migrations.JsonSchemaVersions; +import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.SessionUserService; +import com.appsmith.server.services.WorkspaceService; +import com.google.gson.Gson; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.lang.Boolean.TRUE; + +@Slf4j +public class ExportServiceCEImpl implements ExportServiceCE { + + private final SessionUserService sessionUserService; + private final AnalyticsService analyticsService; + private final WorkspaceService workspaceService; + private final ApplicationExportService applicationExportService; + private final ExportableService datasourceExportableService; + private final ExportableService pluginExportableService; + private final ExportableService customJSLibExportableService; + protected final Gson gson; + + public ExportServiceCEImpl( + SessionUserService sessionUserService, + AnalyticsService analyticsService, + ApplicationExportService applicationExportService, + WorkspaceService workspaceService, + Gson gson, + ExportableService datasourceExportableService, + ExportableService pluginExportableService, + ExportableService customJSLibExportableService) { + this.sessionUserService = sessionUserService; + this.analyticsService = analyticsService; + this.workspaceService = workspaceService; + this.gson = gson; + this.applicationExportService = applicationExportService; + this.datasourceExportableService = datasourceExportableService; + this.pluginExportableService = pluginExportableService; + this.customJSLibExportableService = customJSLibExportableService; + } + + @Override + public ContextBasedExportService getContextBasedExportService(@NonNull ArtifactJsonType artifactJsonType) { + return switch (artifactJsonType) { + case APPLICATION -> applicationExportService; + default -> applicationExportService; + }; + } + + @Override + public Mono exportByExportableArtifactIdAndBranchName( + String artifactId, + String branchName, + SerialiseArtifactObjective objective, + ArtifactJsonType artifactJsonType) { + + // We require this to be present, without this we can't move further ahead + if (artifactJsonType == null) { + return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ARTIFACT_CONTEXT)); + } + + ContextBasedExportService contextBasedExportService = getContextBasedExportService(artifactJsonType); + Map artifactContextConstantMap = contextBasedExportService.getConstantsMap(); + String idConstant = artifactContextConstantMap.get(FieldName.ID); + + if (!StringUtils.hasText(artifactId)) { + return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, idConstant)); + } + + // Start the stopwatch to log the execution time + Stopwatch stopwatch = new Stopwatch(AnalyticsEvents.EXPORT.getEventName()); + + boolean exportWithConfiguration = false; + + /* + since we are merging the method exportByArtifactIdAndBranchName to one method for performance reasons + this step is required for test cases and TemplateServices + */ + SerialiseArtifactObjective serialiseArtifactObjective = + objective == null ? SerialiseArtifactObjective.SHARE : objective; + + boolean isGitSync = SerialiseArtifactObjective.VERSION_CONTROL.equals(serialiseArtifactObjective) + || SerialiseArtifactObjective.KNOWLEDGE_BASE_GENERATION.equals(serialiseArtifactObjective); + + // We need edit permission for git-related tasks, otherwise export permissions are required + AclPermission permission = + contextBasedExportService.getArtifactExportPermission(isGitSync, exportWithConfiguration); + + final MappedExportableResourcesDTO mappedResourcesDTO = new MappedExportableResourcesDTO(); + final ExportingMetaDTO exportingMetaDTO = new ExportingMetaDTO(); + + ArtifactExchangeJson artifactExchangeJson = contextBasedExportService.createNewArtifactExchangeJson(); + // Set json schema version which will be used to check the compatibility while importing the JSON + artifactExchangeJson.setServerSchemaVersion(JsonSchemaVersions.serverVersion); + artifactExchangeJson.setClientSchemaVersion(JsonSchemaVersions.clientVersion); + + // Find the transaction artifact with appropriate permission + Mono exportableArtifactMono = contextBasedExportService + .findExistingArtifactByIdAndBranchName(artifactId, branchName, permission) + .map(transactionArtifact -> { + // Since we have moved the setting of artifactId from the repository, the MetaDTO needs to assigned + // from here + exportingMetaDTO.setArtifactId(transactionArtifact.getId()); + exportingMetaDTO.setBranchName(null); + exportingMetaDTO.setIsGitSync(isGitSync); + exportingMetaDTO.setExportWithConfiguration(exportWithConfiguration); + + if (!TRUE.equals(transactionArtifact.getExportWithConfiguration())) { + // Explicitly setting the boolean to avoid NPE for future checks + transactionArtifact.setExportWithConfiguration(false); + } + exportingMetaDTO.setExportWithConfiguration(transactionArtifact.getExportWithConfiguration()); + return transactionArtifact; + }) + .cache(); + + return exportableArtifactMono + .flatMap(exportableArtifact -> { + // Refactor exportableArtifact to remove the ids + // TODO rename the method + contextBasedExportService.getArtifactReadyForExport( + exportableArtifact, artifactExchangeJson, exportingMetaDTO); + return getExportableEntities( + exportingMetaDTO, mappedResourcesDTO, exportableArtifactMono, artifactExchangeJson) + .then(Mono.defer(() -> sanitizeEntities( + serialiseArtifactObjective, + artifactExchangeJson, + mappedResourcesDTO, + exportingMetaDTO))) + .then(Mono.fromCallable(() -> { + exportableArtifact.makePristine(); + exportableArtifact.sanitiseToExportDBObject(); + // Disable exporting the exportableArtifact with datasource config once imported in + // destination + // instance + exportableArtifact.setExportWithConfiguration(null); + return artifactExchangeJson; + })); + }) + .then(sessionUserService.getCurrentUser()) + .flatMap(user -> { + Map contextConstants = contextBasedExportService.getConstantsMap(); + stopwatch.stopTimer(); + final Map data = new HashMap<>(); + data.put(FieldName.FLOW_NAME, stopwatch.getFlow()); + data.put("executionTime", stopwatch.getExecutionTime()); + data.put(contextConstants.get(FieldName.ID), exportingMetaDTO.getArtifactId()); + data.putAll(contextBasedExportService.getExportRelatedArtifactData(artifactExchangeJson)); + return analyticsService + .sendEvent(AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), user.getUsername(), data) + .thenReturn(artifactExchangeJson); + }) + .flatMap(unused -> sendExportArtifactAnalyticsEvent( + contextBasedExportService, exportingMetaDTO.getArtifactId(), AnalyticsEvents.EXPORT)) + .thenReturn(artifactExchangeJson); + } + + protected Mono sanitizeEntities( + SerialiseArtifactObjective serialiseFor, + ArtifactExchangeJson artifactExchangeJson, + MappedExportableResourcesDTO mappedResourcesDTO, + ExportingMetaDTO exportingMetaDTO) { + + ContextBasedExportService contextBasedExportService = + getContextBasedExportService(artifactExchangeJson.getArtifactJsonType()); + + datasourceExportableService.sanitizeEntities( + exportingMetaDTO, mappedResourcesDTO, artifactExchangeJson, serialiseFor, true); + + contextBasedExportService.sanitizeArtifactSpecificExportableEntities( + exportingMetaDTO, mappedResourcesDTO, artifactExchangeJson, serialiseFor); + + return Mono.empty(); + } + + private Mono getExportableEntities( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedResourcesDTO, + Mono exportableArtifactMono, + ArtifactExchangeJson artifactExchangeJson) { + + ContextBasedExportService contextBasedExportService = + getContextBasedExportService(artifactExchangeJson.getArtifactJsonType()); + + List> artifactAgnosticExportedEntities = generateArtifactAgnosticExportables( + exportingMetaDTO, mappedResourcesDTO, exportableArtifactMono, artifactExchangeJson); + Flux artifactSpecificExportedEntities = contextBasedExportService.generateArtifactSpecificExportables( + exportingMetaDTO, mappedResourcesDTO, exportableArtifactMono, artifactExchangeJson); + Flux artifactComponentDependentExportedEntities = + contextBasedExportService.generateArtifactComponentDependentExportables( + exportingMetaDTO, mappedResourcesDTO, exportableArtifactMono, artifactExchangeJson); + + // The idea with both these methods is that any amount of overriding should take care of whether they want to + // zip the additional exportables along with these or sequence them, or combine them using any other logic + return Flux.merge(artifactAgnosticExportedEntities) + .thenMany(Flux.merge(artifactSpecificExportedEntities)) + .thenMany(Flux.merge(artifactComponentDependentExportedEntities)) + .then(); + } + + protected List> generateArtifactAgnosticExportables( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedResourcesDTO, + Mono exportableArtifactMono, + ArtifactExchangeJson artifactExchangeJson) { + + // Updates plugin map in exportable resources + Mono pluginExportablesMono = pluginExportableService.getExportableEntities( + exportingMetaDTO, mappedResourcesDTO, exportableArtifactMono, artifactExchangeJson, TRUE); + + // Updates datasourceId to name map in exportable resources. + // Also directly updates required datasources information in artifactExchangeJSON + Mono datasourceExportablesMono = datasourceExportableService.getExportableEntities( + exportingMetaDTO, mappedResourcesDTO, exportableArtifactMono, artifactExchangeJson, TRUE); + + // Directly sets required custom JS lib information in artifactExchangeJSON + Mono customJsLibsExportablesMono = customJSLibExportableService.getExportableEntities( + exportingMetaDTO, mappedResourcesDTO, exportableArtifactMono, artifactExchangeJson, TRUE); + + return List.of(pluginExportablesMono, datasourceExportablesMono, customJsLibsExportablesMono); + } + + /** + * This method is for general export flow of any artifact with default branch with the right artifact-Id. + * Since we are moving towards unique-id artifacts for Git (under considerations), this would be main method moving forward. + * @param artifactId : ID of the artifact to be exported + * @param objective : objective of serialisation, it could be for version-control, sharing, or some other purpose + * @param artifactJsonType : Type of Artifact. + * @return A json which extends Artifact exchange json i.e. ApplicationJson + */ + @Override + public Mono exportByArtifactId( + String artifactId, SerialiseArtifactObjective objective, ArtifactJsonType artifactJsonType) { + return exportByExportableArtifactIdAndBranchName(artifactId, null, objective, artifactJsonType); + } + + /** + * This method is explicitly for exporting applications which is present in different branches. + * @param artifactId : ID of the artifact to be exported + * @param branchName : branch name of the artifact in case it's git connected + * @param artifactJsonType : Type of Artifact. + * @return A json which extends Artifact exchange json i.e. ApplicationJson + */ + @Override + public Mono exportByArtifactIdAndBranchName( + String artifactId, String branchName, ArtifactJsonType artifactJsonType) { + return exportByExportableArtifactIdAndBranchName( + artifactId, branchName, SerialiseArtifactObjective.SHARE, artifactJsonType); + } + + public Mono getArtifactFile( + String artifactId, String branchName, ArtifactJsonType artifactJsonType) { + return exportByArtifactIdAndBranchName(artifactId, branchName, artifactJsonType) + .map(artifactExchangeJson -> { + String stringifiedFile = gson.toJson(artifactExchangeJson); + String artifactName = + artifactExchangeJson.getExportableArtifact().getName(); + Object jsonObject = gson.fromJson(stringifiedFile, Object.class); + HttpHeaders responseHeaders = new HttpHeaders(); + ContentDisposition contentDisposition = ContentDisposition.builder("attachment") + .filename(artifactName + ".json", StandardCharsets.UTF_8) + .build(); + responseHeaders.setContentDisposition(contentDisposition); + responseHeaders.setContentType(MediaType.APPLICATION_JSON); + + ExportFileDTO exportFileDTO = new ExportFileDTO(); + exportFileDTO.setArtifactResource(jsonObject); + exportFileDTO.setHttpHeaders(responseHeaders); + return exportFileDTO; + }); + } + + /** + * To send analytics event for import and export of application + * + * @param contextBasedExportService : A exportService which is an implementation of contextBasedExportService + * @param exportableArtifactId : String exportableArtifactId + * @param event : Analytics Event + * @return a subclass of which is imported or exported + */ + private Mono sendExportArtifactAnalyticsEvent( + ContextBasedExportService contextBasedExportService, + String exportableArtifactId, + AnalyticsEvents event) { + return contextBasedExportService + .findExistingArtifactForAnalytics(exportableArtifactId) + .flatMap(exportableArtifact -> { + return workspaceService + .getById(exportableArtifact.getWorkspaceId()) + .flatMap(workspace -> { + Map contextConstants = contextBasedExportService.getConstantsMap(); + final Map data = new HashMap<>(); + final Map eventData = Map.of( + contextConstants.get(FieldName.ARTIFACT_CONTEXT), + exportableArtifact, + FieldName.WORKSPACE, + workspace); + + data.put(FieldName.EVENT_DATA, eventData); + data.put(FieldName.WORKSPACE_ID, workspace.getId()); + data.put(contextConstants.get(FieldName.ID), exportableArtifact.getId()); + return analyticsService.sendObjectEvent(event, exportableArtifact, data); + }); + }); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportServiceImpl.java new file mode 100644 index 0000000000..3871be3d80 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportServiceImpl.java @@ -0,0 +1,37 @@ +package com.appsmith.server.exports.exportable; + +import com.appsmith.external.models.Datasource; +import com.appsmith.server.applications.exports.ApplicationExportService; +import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.Plugin; +import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.SessionUserService; +import com.appsmith.server.services.WorkspaceService; +import com.google.gson.Gson; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class ExportServiceImpl extends ExportServiceCEImpl implements ExportService { + + public ExportServiceImpl( + SessionUserService sessionUserService, + AnalyticsService analyticsService, + ApplicationExportService applicationExportService, + WorkspaceService workspaceService, + Gson gson, + ExportableService datasourceExportableService, + ExportableService pluginExportableService, + ExportableService customJSLibExportableService) { + super( + sessionUserService, + analyticsService, + applicationExportService, + workspaceService, + gson, + datasourceExportableService, + pluginExportableService, + customJSLibExportableService); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportableServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportableServiceCE.java index 33f15a248f..e74959448f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportableServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/exportable/ExportableServiceCE.java @@ -1,9 +1,11 @@ package com.appsmith.server.exports.exportable; import com.appsmith.external.models.BaseDomain; -import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.constants.SerialiseArtifactObjective; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ExportableArtifact; import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.ArtifactExchangeJson; import com.appsmith.server.dtos.ExportingMetaDTO; import com.appsmith.server.dtos.MappedExportableResourcesDTO; import reactor.core.publisher.Mono; @@ -20,11 +22,27 @@ public interface ExportableServiceCE { Mono applicationMono, ApplicationJson applicationJson); + default Mono getExportableEntities( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedExportableResourcesDTO, + Mono exportableArtifactMono, + ArtifactExchangeJson artifactExchangeJson, + Boolean isContextAgnostic) { + return Mono.empty(); + } + default void sanitizeEntities( ExportingMetaDTO exportingMetaDTO, MappedExportableResourcesDTO mappedExportableResourcesDTO, ApplicationJson applicationJson, - SerialiseApplicationObjective serialiseFor) {} + SerialiseArtifactObjective serialiseFor) {} + + default void sanitizeEntities( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedExportableResourcesDTO, + ArtifactExchangeJson artifactExchangeJson, + SerialiseArtifactObjective serialiseFor, + Boolean isContextAgnositc) {} default Set mapNameToIdForExportableEntities( MappedExportableResourcesDTO mappedExportableResourcesDTO, List entityList) { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ContextBasedExportService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ContextBasedExportService.java new file mode 100644 index 0000000000..c69ce5bb37 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ContextBasedExportService.java @@ -0,0 +1,7 @@ +package com.appsmith.server.exports.internal; + +import com.appsmith.server.domains.ExportableArtifact; +import com.appsmith.server.dtos.ArtifactExchangeJson; + +public interface ContextBasedExportService + extends ContextBasedExportServiceCE {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ContextBasedExportServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ContextBasedExportServiceCE.java new file mode 100644 index 0000000000..9830f74912 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ContextBasedExportServiceCE.java @@ -0,0 +1,50 @@ +package com.appsmith.server.exports.internal; + +import com.appsmith.server.acl.AclPermission; +import com.appsmith.server.constants.SerialiseArtifactObjective; +import com.appsmith.server.domains.ExportableArtifact; +import com.appsmith.server.dtos.ArtifactExchangeJson; +import com.appsmith.server.dtos.ExportingMetaDTO; +import com.appsmith.server.dtos.MappedExportableResourcesDTO; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Map; + +public interface ContextBasedExportServiceCE { + + U createNewArtifactExchangeJson(); + + AclPermission getArtifactExportPermission(Boolean isGitSync, Boolean exportWithConfiguration); + + Mono findExistingArtifactByIdAndBranchName(String artifactId, String branchName, AclPermission aclPermission); + + Mono findExistingArtifactForAnalytics(String artifactId); + + void getArtifactReadyForExport( + ExportableArtifact exportableArtifact, + ArtifactExchangeJson artifactExchangeJson, + ExportingMetaDTO exportingMetaDTO); + + Map getExportRelatedArtifactData(ArtifactExchangeJson artifactExchangeJson); + + Map getConstantsMap(); + + void sanitizeArtifactSpecificExportableEntities( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedExportableResourcesDTO, + ArtifactExchangeJson artifactExchangeJson, + SerialiseArtifactObjective serialiseArtifactObjective); + + Flux generateArtifactSpecificExportables( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedResourcesDTO, + Mono exportableArtifactMono, + ArtifactExchangeJson artifactExchangeJson); + + Flux generateArtifactComponentDependentExportables( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedResourcesDTO, + Mono exportableArtifactMono, + ArtifactExchangeJson artifactExchangeJson); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ExportApplicationServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ExportApplicationServiceCE.java index c78dd215a3..bbefaf9ce7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ExportApplicationServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ExportApplicationServiceCE.java @@ -1,6 +1,6 @@ package com.appsmith.server.exports.internal; -import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.constants.SerialiseArtifactObjective; import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.dtos.ExportFileDTO; import reactor.core.publisher.Mono; @@ -13,7 +13,7 @@ public interface ExportApplicationServiceCE { * @param applicationId which needs to be exported * @return application reference from which entire application can be rehydrated */ - Mono exportApplicationById(String applicationId, SerialiseApplicationObjective serialiseFor); + Mono exportApplicationById(String applicationId, SerialiseArtifactObjective serialiseFor); Mono exportApplicationById(String applicationId, String branchName); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ExportApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ExportApplicationServiceCEImpl.java index b140ef51f6..27a0f50aae 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ExportApplicationServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/ExportApplicationServiceCEImpl.java @@ -6,7 +6,7 @@ import com.appsmith.external.models.Datasource; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.applications.base.ApplicationService; import com.appsmith.server.constants.FieldName; -import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.constants.SerialiseArtifactObjective; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationPage; @@ -71,8 +71,7 @@ public class ExportApplicationServiceCEImpl implements ExportApplicationServiceC * @param applicationId which needs to be exported * @return application reference from which entire application can be rehydrated */ - public Mono exportApplicationById( - String applicationId, SerialiseApplicationObjective serialiseFor) { + public Mono exportApplicationById(String applicationId, SerialiseArtifactObjective serialiseFor) { // Start the stopwatch to log the execution time Stopwatch stopwatch = new Stopwatch(AnalyticsEvents.EXPORT.getEventName()); @@ -87,15 +86,15 @@ public class ExportApplicationServiceCEImpl implements ExportApplicationServiceC ApplicationJson applicationJson = new ApplicationJson(); final MappedExportableResourcesDTO mappedResourcesDTO = new MappedExportableResourcesDTO(); final ExportingMetaDTO exportingMetaDTO = new ExportingMetaDTO(); - exportingMetaDTO.setApplicationId(applicationId); + exportingMetaDTO.setArtifactId(applicationId); exportingMetaDTO.setBranchName(null); if (applicationId == null || applicationId.isEmpty()) { return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.APPLICATION_ID)); } - boolean isGitSync = SerialiseApplicationObjective.VERSION_CONTROL.equals(serialiseFor) - || SerialiseApplicationObjective.KNOWLEDGE_BASE_GENERATION.equals(serialiseFor); + boolean isGitSync = SerialiseArtifactObjective.VERSION_CONTROL.equals(serialiseFor) + || SerialiseArtifactObjective.KNOWLEDGE_BASE_GENERATION.equals(serialiseFor); exportingMetaDTO.setIsGitSync(isGitSync); exportingMetaDTO.setExportWithConfiguration(false); @@ -135,7 +134,7 @@ public class ExportApplicationServiceCEImpl implements ExportApplicationServiceC !JsonSchemaVersions.clientVersion.equals(application.getClientSchemaVersion()); boolean isServerSchemaMigrated = !JsonSchemaVersions.serverVersion.equals(application.getServerSchemaVersion()); - exportingMetaDTO.setApplicationLastCommittedAt(applicationLastCommittedAt); + exportingMetaDTO.setArtifactLastCommittedAt(applicationLastCommittedAt); exportingMetaDTO.setClientSchemaMigrated(isClientSchemaMigrated); exportingMetaDTO.setServerSchemaMigrated(isServerSchemaMigrated); applicationJson.setExportedApplication(application); @@ -145,7 +144,7 @@ public class ExportApplicationServiceCEImpl implements ExportApplicationServiceC .map(ApplicationPage::getId) .collect(Collectors.toList()); - exportingMetaDTO.setUnpublishedPages(unpublishedPages); + exportingMetaDTO.setUnpublishedModulesOrPages(unpublishedPages); return getExportableEntities(exportingMetaDTO, mappedResourcesDTO, applicationMono, applicationJson) .then(Mono.defer(() -> sanitizeEntities( @@ -184,7 +183,7 @@ public class ExportApplicationServiceCEImpl implements ExportApplicationServiceC } protected Mono sanitizeEntities( - SerialiseApplicationObjective serialiseFor, + SerialiseArtifactObjective serialiseFor, ApplicationJson applicationJson, MappedExportableResourcesDTO mappedResourcesDTO, ExportingMetaDTO exportingMetaDTO) { @@ -274,7 +273,7 @@ public class ExportApplicationServiceCEImpl implements ExportApplicationServiceC public Mono exportApplicationById(String applicationId, String branchName) { return applicationService .findBranchedApplicationId(branchName, applicationId, applicationPermission.getExportPermission()) - .flatMap(branchedAppId -> exportApplicationById(branchedAppId, SerialiseApplicationObjective.SHARE)); + .flatMap(branchedAppId -> exportApplicationById(branchedAppId, SerialiseArtifactObjective.SHARE)); } public Mono getApplicationFile(String applicationId, String branchName) { @@ -290,7 +289,7 @@ public class ExportApplicationServiceCEImpl implements ExportApplicationServiceC responseHeaders.setContentType(MediaType.APPLICATION_JSON); ExportFileDTO exportFileDTO = new ExportFileDTO(); - exportFileDTO.setApplicationResource(jsonObject); + exportFileDTO.setArtifactResource(jsonObject); exportFileDTO.setHttpHeaders(responseHeaders); return exportFileDTO; }); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/PartialExportServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/PartialExportServiceCEImpl.java index 80f99d269b..9b8ea65501 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/PartialExportServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exports/internal/PartialExportServiceCEImpl.java @@ -8,7 +8,7 @@ import com.appsmith.server.acl.AclPermission; import com.appsmith.server.actioncollections.base.ActionCollectionService; import com.appsmith.server.applications.base.ApplicationService; import com.appsmith.server.constants.FieldName; -import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.constants.SerialiseArtifactObjective; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.CustomJSLib; @@ -70,7 +70,7 @@ public class PartialExportServiceCEImpl implements PartialExportServiceCE { final MappedExportableResourcesDTO mappedResourcesDTO = new MappedExportableResourcesDTO(); final ExportingMetaDTO exportingMetaDTO = new ExportingMetaDTO(); - exportingMetaDTO.setApplicationId(applicationId); + exportingMetaDTO.setArtifactId(applicationId); exportingMetaDTO.setBranchName(null); exportingMetaDTO.setIsGitSync(false); exportingMetaDTO.setExportWithConfiguration(false); @@ -164,7 +164,7 @@ public class PartialExportServiceCEImpl implements PartialExportServiceCE { exportingMetaDTO, mappedResourcesDTO, applicationJson, - SerialiseApplicationObjective.SHARE); + SerialiseArtifactObjective.SHARE); } return Mono.just(applicationJson).zipWith(sessionUserService.getCurrentUser()); }) @@ -266,8 +266,8 @@ public class PartialExportServiceCEImpl implements PartialExportServiceCE { private Mono updatePageNameInResourceMapDTO( String pageId, MappedExportableResourcesDTO mappedResourcesDTO) { return newPageService.getNameByPageId(pageId, false).flatMap(pageName -> { - mappedResourcesDTO.getPageIdToNameMap().put(pageId + EDIT, pageName); - mappedResourcesDTO.getPageIdToNameMap().put(pageId + VIEW, pageName); + mappedResourcesDTO.getPageOrModuleIdToNameMap().put(pageId + EDIT, pageName); + mappedResourcesDTO.getPageOrModuleIdToNameMap().put(pageId + VIEW, pageName); return Mono.just(pageId); }); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceCEImpl.java index f08677cf46..7645debcdc 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/imports/internal/ImportServiceCEImpl.java @@ -41,8 +41,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import static com.appsmith.server.constants.ArtifactJsonType.APPLICATION; - @Slf4j public class ImportServiceCEImpl implements ImportServiceCE { @@ -58,7 +56,6 @@ public class ImportServiceCEImpl implements ImportServiceCE { private final ImportableService pluginImportableService; private final ImportableService datasourceImportableService; private final ImportableService themeImportableService; - private final Map> serviceFactory = new HashMap<>(); public ImportServiceCEImpl( ApplicationImportService applicationImportService, @@ -81,7 +78,6 @@ public class ImportServiceCEImpl implements ImportServiceCE { this.pluginImportableService = pluginImportableService; this.datasourceImportableService = datasourceImportableService; this.themeImportableService = themeImportableService; - serviceFactory.put(APPLICATION, applicationImportService); } /** @@ -107,7 +103,10 @@ public class ImportServiceCEImpl implements ImportServiceCE { public ContextBasedImportService< ? extends ImportableArtifact, ? extends ImportableArtifactDTO, ? extends ArtifactExchangeJson> getContextBasedImportService(ArtifactJsonType artifactJsonType) { - return serviceFactory.getOrDefault(artifactJsonType, applicationImportService); + return switch (artifactJsonType) { + case APPLICATION -> applicationImportService; + default -> applicationImportService; + }; } /** diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/jslibs/exports/CustomJSLibExportableServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/jslibs/exports/CustomJSLibExportableServiceCEImpl.java index ec72aff9ea..561555d985 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/jslibs/exports/CustomJSLibExportableServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/jslibs/exports/CustomJSLibExportableServiceCEImpl.java @@ -4,8 +4,10 @@ import com.appsmith.external.models.CreatorContextType; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.ExportableArtifact; import com.appsmith.server.domains.GitApplicationMetadata; import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.ArtifactExchangeJson; import com.appsmith.server.dtos.ExportingMetaDTO; import com.appsmith.server.dtos.MappedExportableResourcesDTO; import com.appsmith.server.exports.exportable.ExportableServiceCE; @@ -77,9 +79,24 @@ public class CustomJSLibExportableServiceCEImpl implements ExportableServiceCE getExportableEntities( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedExportableResourcesDTO, + Mono exportableArtifactMono, + ArtifactExchangeJson artifactExchangeJson, + Boolean isContextAgnostic) { + return exportableArtifactMono.flatMap(exportableArtifact -> { + Mono applicationMono = Mono.just((Application) exportableArtifact); + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + return getExportableEntities( + exportingMetaDTO, mappedExportableResourcesDTO, applicationMono, applicationJson); + }); + } + protected Mono> getAllJSLibsInContext(ExportingMetaDTO exportingMetaDTO) { return customJSLibService.getAllJSLibsInContext( - exportingMetaDTO.getApplicationId(), + exportingMetaDTO.getArtifactId(), CreatorContextType.APPLICATION, exportingMetaDTO.getBranchName(), false); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/exports/NewActionExportableServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/exports/NewActionExportableServiceCEImpl.java index 4a8433df23..8282c6994b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/exports/NewActionExportableServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/exports/NewActionExportableServiceCEImpl.java @@ -50,8 +50,8 @@ public class NewActionExportableServiceCEImpl implements ExportableServiceCE optionalPermission = Optional.ofNullable(actionPermission.getExportPermission( exportingMetaDTO.getIsGitSync(), exportingMetaDTO.getExportWithConfiguration())); - Flux actionFlux = - newActionService.findByPageIdsForExport(exportingMetaDTO.getUnpublishedPages(), optionalPermission); + Flux actionFlux = newActionService.findByPageIdsForExport( + exportingMetaDTO.getUnpublishedModulesOrPages(), optionalPermission); return actionFlux .collectList() @@ -78,19 +78,17 @@ public class NewActionExportableServiceCEImpl implements ExportableServiceCE optionalPermission = Optional.ofNullable(pagePermission.getExportPermission( exportingMetaDTO.getIsGitSync(), exportingMetaDTO.getExportWithConfiguration())); - List unpublishedPages = exportingMetaDTO.getUnpublishedPages(); + List unpublishedPages = exportingMetaDTO.getUnpublishedModulesOrPages(); return newPageService - .findNewPagesByApplicationId(exportingMetaDTO.getApplicationId(), optionalPermission) + .findNewPagesByApplicationId(exportingMetaDTO.getArtifactId(), optionalPermission) .collectList() .map(newPageList -> { // Extract mongoEscapedWidgets from pages and save it to applicationJson object as this @@ -65,7 +65,7 @@ public class NewPageExportableServiceCEImpl implements ExportableServiceCE { if (newPage.getUnpublishedPage() != null) { mappedExportableResourcesDTO - .getPageIdToNameMap() + .getPageOrModuleIdToNameMap() .put( newPage.getId() + EDIT, newPage.getUnpublishedPage().getName()); @@ -79,7 +79,7 @@ public class NewPageExportableServiceCEImpl implements ExportableServiceCE getExportableEntities( + ExportingMetaDTO exportingMetaDTO, + MappedExportableResourcesDTO mappedExportableResourcesDTO, + Mono exportableArtifactMono, + ArtifactExchangeJson artifactExchangeJson, + Boolean isContextAgnostic) { + return exportableArtifactMono.flatMap(exportableArtifact -> { + Mono applicationMono = Mono.just((Application) exportableArtifact); + ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson; + return getExportableEntities( + exportingMetaDTO, mappedExportableResourcesDTO, applicationMono, applicationJson); + }); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationSnapshotServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationSnapshotServiceCEImpl.java index d80af591a2..e7f34336c6 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationSnapshotServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationSnapshotServiceCEImpl.java @@ -2,7 +2,7 @@ package com.appsmith.server.services.ce; import com.appsmith.server.applications.base.ApplicationService; import com.appsmith.server.constants.FieldName; -import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.constants.SerialiseArtifactObjective; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationSnapshot; import com.appsmith.server.dtos.ApplicationJson; @@ -40,12 +40,12 @@ public class ApplicationSnapshotServiceCEImpl implements ApplicationSnapshotServ public Mono createApplicationSnapshot(String applicationId, String branchName) { return applicationService .findBranchedApplicationId(branchName, applicationId, applicationPermission.getEditPermission()) - /* SerialiseApplicationObjective=VERSION_CONTROL because this API can be invoked from developers. - exportApplicationById method check for MANAGE_PERMISSION if SerialiseApplicationObjective=SHARE. + /* SerialiseArtifactObjective=VERSION_CONTROL because this API can be invoked from developers. + exportApplicationById method check for MANAGE_PERMISSION if SerialiseArtifactObjective=SHARE. */ .flatMap(branchedAppId -> Mono.zip( exportApplicationService.exportApplicationById( - branchedAppId, SerialiseApplicationObjective.VERSION_CONTROL), + branchedAppId, SerialiseArtifactObjective.VERSION_CONTROL), Mono.just(branchedAppId))) .flatMapMany(objects -> { String branchedAppId = objects.getT2(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GitServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GitServiceCEImpl.java index e3758788f0..f11e1b6adb 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GitServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GitServiceCEImpl.java @@ -18,7 +18,7 @@ import com.appsmith.server.constants.Assets; import com.appsmith.server.constants.Entity; import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.GitDefaultCommitMessage; -import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.constants.SerialiseArtifactObjective; import com.appsmith.server.datasources.base.DatasourceService; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationMode; @@ -487,7 +487,7 @@ public class GitServiceCEImpl implements GitServiceCE { } return Mono.zip( exportApplicationService.exportApplicationById( - branchedApplication.getId(), SerialiseApplicationObjective.VERSION_CONTROL), + branchedApplication.getId(), SerialiseArtifactObjective.VERSION_CONTROL), Mono.just(branchedApplication)); }) .flatMap(tuple -> { @@ -838,7 +838,7 @@ public class GitServiceCEImpl implements GitServiceCE { // Set branchName for each application resource return exportApplicationService .exportApplicationById( - applicationId, SerialiseApplicationObjective.VERSION_CONTROL) + applicationId, SerialiseArtifactObjective.VERSION_CONTROL) .flatMap(applicationJson -> { applicationJson .getExportedApplication() @@ -1328,7 +1328,7 @@ public class GitServiceCEImpl implements GitServiceCE { return Mono.zip( applicationService.save(srcApplication), exportApplicationService.exportApplicationById( - srcApplicationId, SerialiseApplicationObjective.VERSION_CONTROL)); + srcApplicationId, SerialiseArtifactObjective.VERSION_CONTROL)); }) .onErrorResume(error -> Mono.error(new AppsmithException( AppsmithError.GIT_ACTION_FAILED, "branch", error.getMessage()))); @@ -1917,7 +1917,7 @@ public class GitServiceCEImpl implements GitServiceCE { } Mono exportAppMono = exportApplicationService.exportApplicationById( - application.getId(), SerialiseApplicationObjective.VERSION_CONTROL); + application.getId(), SerialiseArtifactObjective.VERSION_CONTROL); return Mono.zip(exportAppMono, fetchRemoteMono) // zip will run them in parallel .map(Tuple2::getT1); @@ -2463,7 +2463,7 @@ public class GitServiceCEImpl implements GitServiceCE { .findByBranchNameAndDefaultApplicationId( branchName, defaultApplicationId, applicationPermission.getEditPermission()) .zipWhen(application -> exportApplicationService.exportApplicationById( - application.getId(), SerialiseApplicationObjective.VERSION_CONTROL))) + application.getId(), SerialiseArtifactObjective.VERSION_CONTROL))) .flatMap(tuple -> { GitApplicationMetadata defaultApplicationMetadata = tuple.getT1(); Application application = tuple.getT2().getT1(); diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/controllers/ApplicationControllerTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/controllers/ApplicationControllerTest.java index b40f704293..8c3e2525ee 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/controllers/ApplicationControllerTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/controllers/ApplicationControllerTest.java @@ -7,6 +7,7 @@ import com.appsmith.server.configurations.SecurityTestConfig; import com.appsmith.server.constants.Url; import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.exceptions.AppsmithErrorCode; +import com.appsmith.server.exports.exportable.ExportService; import com.appsmith.server.exports.internal.ExportApplicationService; import com.appsmith.server.exports.internal.PartialExportService; import com.appsmith.server.fork.internal.ApplicationForkingService; @@ -62,6 +63,9 @@ public class ApplicationControllerTest { @MockBean ImportService importService; + @MockBean + ExportService exportService; + @MockBean ExportApplicationService exportApplicationService; diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/exports/internal/ExportServiceTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/exports/internal/ExportServiceTests.java new file mode 100644 index 0000000000..207e64aedb --- /dev/null +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/exports/internal/ExportServiceTests.java @@ -0,0 +1,2096 @@ +package com.appsmith.server.exports.internal; + +import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionDTO; +import com.appsmith.external.models.BearerTokenAuth; +import com.appsmith.external.models.Connection; +import com.appsmith.external.models.CreatorContextType; +import com.appsmith.external.models.DBAuth; +import com.appsmith.external.models.Datasource; +import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.DatasourceStorage; +import com.appsmith.external.models.DatasourceStorageDTO; +import com.appsmith.external.models.DecryptedSensitiveFields; +import com.appsmith.external.models.InvisibleActionFields; +import com.appsmith.external.models.PluginType; +import com.appsmith.external.models.Policy; +import com.appsmith.external.models.Property; +import com.appsmith.external.models.SSLDetails; +import com.appsmith.server.actioncollections.base.ActionCollectionService; +import com.appsmith.server.applications.base.ApplicationService; +import com.appsmith.server.constants.ArtifactJsonType; +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.constants.SerialiseArtifactObjective; +import com.appsmith.server.datasources.base.DatasourceService; +import com.appsmith.server.domains.ActionCollection; +import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ApplicationDetail; +import com.appsmith.server.domains.ApplicationPage; +import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.GitApplicationMetadata; +import com.appsmith.server.domains.Layout; +import com.appsmith.server.domains.NewAction; +import com.appsmith.server.domains.NewPage; +import com.appsmith.server.domains.PermissionGroup; +import com.appsmith.server.domains.Plugin; +import com.appsmith.server.domains.Theme; +import com.appsmith.server.domains.User; +import com.appsmith.server.domains.Workspace; +import com.appsmith.server.dtos.ActionCollectionDTO; +import com.appsmith.server.dtos.ApplicationAccessDTO; +import com.appsmith.server.dtos.ApplicationJson; +import com.appsmith.server.dtos.PageDTO; +import com.appsmith.server.exceptions.AppsmithError; +import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.exports.exportable.ExportService; +import com.appsmith.server.helpers.MockPluginExecutor; +import com.appsmith.server.helpers.PluginExecutorHelper; +import com.appsmith.server.imports.importable.ImportService; +import com.appsmith.server.jslibs.base.CustomJSLibService; +import com.appsmith.server.layouts.UpdateLayoutService; +import com.appsmith.server.migrations.JsonSchemaVersions; +import com.appsmith.server.newactions.base.NewActionService; +import com.appsmith.server.newpages.base.NewPageService; +import com.appsmith.server.repositories.ApplicationRepository; +import com.appsmith.server.repositories.CacheableRepositoryHelper; +import com.appsmith.server.repositories.PermissionGroupRepository; +import com.appsmith.server.repositories.PluginRepository; +import com.appsmith.server.services.ApplicationPageService; +import com.appsmith.server.services.LayoutActionService; +import com.appsmith.server.services.LayoutCollectionService; +import com.appsmith.server.services.PermissionGroupService; +import com.appsmith.server.services.SessionUserService; +import com.appsmith.server.services.WorkspaceService; +import com.appsmith.server.solutions.EnvironmentPermission; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpMethod; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static com.appsmith.external.constants.GitConstants.NAME_SEPARATOR; +import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS; +import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES; +import static com.appsmith.server.acl.AclPermission.READ_ACTIONS; +import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS; +import static com.appsmith.server.acl.AclPermission.READ_PAGES; +import static com.appsmith.server.acl.AclPermission.READ_WORKSPACES; +import static com.appsmith.server.constants.FieldName.DEFAULT_PAGE_LAYOUT; +import static com.appsmith.server.dtos.CustomJSLibContextDTO.getDTOFromCustomJSLib; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +@Slf4j +@ExtendWith(SpringExtension.class) +@SpringBootTest +@DirtiesContext +@TestMethodOrder(MethodOrderer.MethodName.class) +public class ExportServiceTests { + + private static final String INVALID_JSON_FILE = "invalid json file"; + private static final Map datasourceMap = new HashMap<>(); + private static Plugin installedPlugin; + private static String workspaceId; + private static String defaultEnvironmentId; + private static String testAppId; + private static Datasource jsDatasource; + private static Plugin installedJsPlugin; + private static Boolean isSetupDone = false; + private static String exportWithConfigurationAppId; + + @Autowired + ExportService exportService; + + @Autowired + ImportService importService; + + @Autowired + ApplicationPageService applicationPageService; + + @Autowired + PluginRepository pluginRepository; + + @Autowired + ApplicationRepository applicationRepository; + + @Autowired + DatasourceService datasourceService; + + @Autowired + NewPageService newPageService; + + @Autowired + NewActionService newActionService; + + @Autowired + WorkspaceService workspaceService; + + @Autowired + LayoutActionService layoutActionService; + + @Autowired + UpdateLayoutService updateLayoutService; + + @Autowired + LayoutCollectionService layoutCollectionService; + + @Autowired + ActionCollectionService actionCollectionService; + + @MockBean + PluginExecutorHelper pluginExecutorHelper; + + @Autowired + ApplicationService applicationService; + + @Autowired + PermissionGroupRepository permissionGroupRepository; + + @Autowired + PermissionGroupService permissionGroupService; + + @Autowired + CustomJSLibService customJSLibService; + + @Autowired + EnvironmentPermission environmentPermission; + + @Autowired + CacheableRepositoryHelper cacheableRepositoryHelper; + + @Autowired + SessionUserService sessionUserService; + + @BeforeEach + public void setup() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())) + .thenReturn(Mono.just(new MockPluginExecutor())); + + if (Boolean.TRUE.equals(isSetupDone)) { + return; + } + User currentUser = sessionUserService.getCurrentUser().block(); + Set beforeCreatingWorkspace = + cacheableRepositoryHelper.getPermissionGroupsOfUser(currentUser).block(); + log.info("Permission Groups for User before creating workspace: {}", beforeCreatingWorkspace); + installedPlugin = pluginRepository.findByPackageName("installed-plugin").block(); + Workspace workspace = new Workspace(); + workspace.setName("Import-Export-Test-Workspace"); + Workspace savedWorkspace = workspaceService.create(workspace).block(); + workspaceId = savedWorkspace.getId(); + defaultEnvironmentId = workspaceService + .getDefaultEnvironmentId(workspaceId, environmentPermission.getExecutePermission()) + .block(); + Set afterCreatingWorkspace = + cacheableRepositoryHelper.getPermissionGroupsOfUser(currentUser).block(); + log.info("Permission Groups for User after creating workspace: {}", afterCreatingWorkspace); + + log.info("Workspace ID: {}", workspaceId); + log.info("Workspace Role Ids: {}", workspace.getDefaultPermissionGroups()); + log.info("Policy for created Workspace: {}", workspace.getPolicies()); + log.info("Current User ID: {}", currentUser.getId()); + + Application testApplication = new Application(); + testApplication.setName("Export-Application-Test-Application"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + + Application.ThemeSetting themeSettings = getThemeSetting(); + testApplication.setUnpublishedApplicationDetail(new ApplicationDetail()); + testApplication.getUnpublishedApplicationDetail().setThemeSetting(themeSettings); + + Application savedApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .block(); + testAppId = savedApplication.getId(); + + Datasource ds1 = new Datasource(); + ds1.setName("DS1"); + ds1.setWorkspaceId(workspaceId); + ds1.setPluginId(installedPlugin.getId()); + final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setUrl("http://example.org/get"); + datasourceConfiguration.setHeaders(List.of(new Property("X-Answer", "42"))); + + HashMap storages1 = new HashMap<>(); + storages1.put( + defaultEnvironmentId, new DatasourceStorageDTO(null, defaultEnvironmentId, datasourceConfiguration)); + ds1.setDatasourceStorages(storages1); + + Datasource ds2 = new Datasource(); + ds2.setName("DS2"); + ds2.setPluginId(installedPlugin.getId()); + ds2.setWorkspaceId(workspaceId); + DatasourceConfiguration datasourceConfiguration2 = new DatasourceConfiguration(); + DBAuth auth = new DBAuth(); + auth.setPassword("awesome-password"); + datasourceConfiguration2.setAuthentication(auth); + HashMap storages2 = new HashMap<>(); + storages2.put( + defaultEnvironmentId, new DatasourceStorageDTO(null, defaultEnvironmentId, datasourceConfiguration2)); + ds2.setDatasourceStorages(storages2); + + jsDatasource = new Datasource(); + jsDatasource.setName("Default JS datasource"); + jsDatasource.setWorkspaceId(workspaceId); + installedJsPlugin = + pluginRepository.findByPackageName("installed-js-plugin").block(); + assert installedJsPlugin != null; + jsDatasource.setPluginId(installedJsPlugin.getId()); + + ds1 = datasourceService.create(ds1).block(); + ds2 = datasourceService.create(ds2).block(); + datasourceMap.put("DS1", ds1); + datasourceMap.put("DS2", ds2); + isSetupDone = true; + } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplicationWithNullApplicationIdTest() { + Mono resultMono = exportService + .exportByArtifactIdAndBranchName(null, "", ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + + StepVerifier.create(resultMono) + .expectErrorMatches(throwable -> throwable instanceof AppsmithException + && throwable + .getMessage() + .equals(AppsmithError.INVALID_PARAMETER.getMessage(FieldName.APPLICATION_ID))) + .verify(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportPublicApplicationTest() { + + Application application = new Application(); + application.setName("exportPublicApplicationTest-Test"); + + Application createdApplication = applicationPageService + .createApplication(application, workspaceId) + .block(); + + Mono workspaceResponse = workspaceService.findById(workspaceId, READ_WORKSPACES); + + ApplicationAccessDTO applicationAccessDTO = new ApplicationAccessDTO(); + applicationAccessDTO.setPublicAccess(true); + + // Make the application public + applicationService + .changeViewAccess(createdApplication.getId(), applicationAccessDTO) + .block(); + + Mono resultMono = exportService + .exportByArtifactIdAndBranchName(createdApplication.getId(), "", ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + + StepVerifier.create(resultMono) + .assertNext(applicationJson -> { + Application exportedApplication = applicationJson.getExportedApplication(); + assertThat(exportedApplication).isNotNull(); + // Assert that the exported application is NOT public + assertThat(exportedApplication.getPolicies()).isNullOrEmpty(); + }) + .verifyComplete(); + } + + @Test + @Disabled + @WithUserDetails(value = "api_user") + public void exportApplication_withInvalidApplicationId_throwNoResourceFoundException() { + Mono resultMono = exportService + .exportByArtifactIdAndBranchName("invalidAppId", "", ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + + StepVerifier.create(resultMono) + .expectErrorMatches(throwable -> throwable instanceof AppsmithException + && throwable + .getMessage() + .equals(AppsmithError.NO_RESOURCE_FOUND.getMessage( + FieldName.APPLICATION_ID, "invalidAppId"))) + .verify(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplicationById_WhenContainsInternalFields_InternalFieldsNotExported() { + Mono resultMono = exportService + .exportByArtifactIdAndBranchName(testAppId, "", ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + + StepVerifier.create(resultMono) + .assertNext(applicationJson -> { + Application exportedApplication = applicationJson.getExportedApplication(); + assertThat(exportedApplication.getModifiedBy()).isNull(); + assertThat(exportedApplication.getLastUpdateTime()).isNull(); + assertThat(exportedApplication.getLastEditedAt()).isNull(); + assertThat(exportedApplication.getLastDeployedAt()).isNull(); + assertThat(exportedApplication.getGitApplicationMetadata()).isNull(); + assertThat(exportedApplication.getEditModeThemeId()).isNull(); + assertThat(exportedApplication.getPublishedModeThemeId()).isNull(); + assertThat(exportedApplication.getExportWithConfiguration()).isNull(); + assertThat(exportedApplication.getForkWithConfiguration()).isNull(); + assertThat(exportedApplication.getForkingEnabled()).isNull(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void createExportAppJsonWithDatasourceButWithoutActionsTest() { + + Application testApplication = new Application(); + testApplication.setName("Another Export Application"); + + final Mono resultMono = workspaceService + .getById(workspaceId) + .flatMap(workspace -> { + final Datasource ds1 = datasourceMap.get("DS1"); + ds1.setWorkspaceId(workspace.getId()); + + final Datasource ds2 = datasourceMap.get("DS2"); + ds2.setWorkspaceId(workspace.getId()); + + return applicationPageService.createApplication(testApplication, workspaceId); + }) + .flatMap(application -> exportService.exportByArtifactIdAndBranchName( + application.getId(), "", ArtifactJsonType.APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + + StepVerifier.create(resultMono) + .assertNext(applicationJson -> { + assertThat(applicationJson.getPageList()).hasSize(1); + assertThat(applicationJson.getActionList()).isEmpty(); + assertThat(applicationJson.getDatasourceList()).isEmpty(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void createExportAppJsonWithActionAndActionCollectionTest() { + + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("template-org-with-ds"); + + Application testApplication = new Application(); + testApplication.setName("ApplicationWithActionCollectionAndDatasource"); + testApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .block(); + + assert testApplication != null; + final String appName = testApplication.getName(); + final Mono resultMono = Mono.zip( + Mono.just(testApplication), + newPageService.findPageById( + testApplication.getPages().get(0).getId(), READ_PAGES, false)) + .flatMap(tuple -> { + Application testApp = tuple.getT1(); + PageDTO testPage = tuple.getT2(); + + Layout layout = testPage.getLayouts().get(0); + ObjectMapper objectMapper = new ObjectMapper(); + JSONObject dsl = new JSONObject(); + try { + dsl = new JSONObject(objectMapper.readValue( + DEFAULT_PAGE_LAYOUT, new TypeReference>() {})); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + ArrayList children = (ArrayList) dsl.get("children"); + JSONObject testWidget = new JSONObject(); + testWidget.put("widgetName", "firstWidget"); + JSONArray temp = new JSONArray(); + temp.add(new JSONObject(Map.of("key", "testField"))); + testWidget.put("dynamicBindingPathList", temp); + testWidget.put("testField", "{{ validAction.data }}"); + children.add(testWidget); + + JSONObject tableWidget = new JSONObject(); + tableWidget.put("widgetName", "Table1"); + tableWidget.put("type", "TABLE_WIDGET"); + Map primaryColumns = new HashMap<>(); + JSONObject jsonObject = new JSONObject(Map.of("key", "value")); + primaryColumns.put("_id", "{{ PageAction.data }}"); + primaryColumns.put("_class", jsonObject); + tableWidget.put("primaryColumns", primaryColumns); + final ArrayList objects = new ArrayList<>(); + JSONArray temp2 = new JSONArray(); + temp2.add(new JSONObject(Map.of("key", "primaryColumns._id"))); + tableWidget.put("dynamicBindingPathList", temp2); + children.add(tableWidget); + + layout.setDsl(dsl); + layout.setPublishedDsl(dsl); + + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(testPage.getId()); + action.setExecuteOnLoad(true); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasourceMap.get("DS2")); + + ActionDTO action2 = new ActionDTO(); + action2.setName("validAction2"); + action2.setPageId(testPage.getId()); + action2.setExecuteOnLoad(true); + action2.setUserSetOnLoad(true); + ActionConfiguration actionConfiguration2 = new ActionConfiguration(); + actionConfiguration2.setHttpMethod(HttpMethod.GET); + action2.setActionConfiguration(actionConfiguration2); + action2.setDatasource(datasourceMap.get("DS2")); + + ActionCollectionDTO actionCollectionDTO1 = new ActionCollectionDTO(); + actionCollectionDTO1.setName("testCollection1"); + actionCollectionDTO1.setPageId(testPage.getId()); + actionCollectionDTO1.setApplicationId(testApp.getId()); + actionCollectionDTO1.setWorkspaceId(testApp.getWorkspaceId()); + actionCollectionDTO1.setPluginId(jsDatasource.getPluginId()); + ActionDTO action1 = new ActionDTO(); + action1.setName("testAction1"); + action1.setActionConfiguration(new ActionConfiguration()); + action1.getActionConfiguration().setBody("mockBody"); + actionCollectionDTO1.setActions(List.of(action1)); + actionCollectionDTO1.setPluginType(PluginType.JS); + + return layoutCollectionService + .createCollection(actionCollectionDTO1, null) + .then(layoutActionService.createSingleAction(action, Boolean.FALSE)) + .then(layoutActionService.createSingleAction(action2, Boolean.FALSE)) + .then(updateLayoutService.updateLayout( + testPage.getId(), testPage.getApplicationId(), layout.getId(), layout)) + .then(exportService.exportByArtifactIdAndBranchName( + testApp.getId(), "", ArtifactJsonType.APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + }) + .cache(); + + Mono> actionListMono = resultMono.then(newActionService + .findAllByApplicationIdAndViewMode(testApplication.getId(), false, READ_ACTIONS, null) + .collectList()); + + Mono> collectionListMono = resultMono.then(actionCollectionService + .findAllByApplicationIdAndViewMode(testApplication.getId(), false, READ_ACTIONS, null) + .collectList()); + + Mono> pageListMono = resultMono.then(newPageService + .findNewPagesByApplicationId(testApplication.getId(), READ_PAGES) + .collectList()); + + StepVerifier.create(Mono.zip(resultMono, actionListMono, collectionListMono, pageListMono)) + .assertNext(tuple -> { + ApplicationJson applicationJson = tuple.getT1(); + List DBActions = tuple.getT2(); + List DBCollections = tuple.getT3(); + List DBPages = tuple.getT4(); + + Application exportedApp = applicationJson.getExportedApplication(); + List pageList = applicationJson.getPageList(); + List actionList = applicationJson.getActionList(); + List actionCollectionList = applicationJson.getActionCollectionList(); + List datasourceList = applicationJson.getDatasourceList(); + + List exportedCollectionIds = actionCollectionList.stream() + .map(ActionCollection::getId) + .collect(Collectors.toList()); + List exportedActionIds = + actionList.stream().map(NewAction::getId).collect(Collectors.toList()); + List DBCollectionIds = + DBCollections.stream().map(ActionCollection::getId).collect(Collectors.toList()); + List DBActionIds = + DBActions.stream().map(NewAction::getId).collect(Collectors.toList()); + List DBOnLayoutLoadActionIds = new ArrayList<>(); + List exportedOnLayoutLoadActionIds = new ArrayList<>(); + + assertThat(DBPages).hasSize(1); + DBPages.forEach( + newPage -> newPage.getUnpublishedPage().getLayouts().forEach(layout -> { + if (layout.getLayoutOnLoadActions() != null) { + layout.getLayoutOnLoadActions().forEach(dslActionDTOSet -> { + dslActionDTOSet.forEach( + actionDTO -> DBOnLayoutLoadActionIds.add(actionDTO.getId())); + }); + } + })); + pageList.forEach( + newPage -> newPage.getUnpublishedPage().getLayouts().forEach(layout -> { + if (layout.getLayoutOnLoadActions() != null) { + layout.getLayoutOnLoadActions().forEach(dslActionDTOSet -> { + dslActionDTOSet.forEach( + actionDTO -> exportedOnLayoutLoadActionIds.add(actionDTO.getId())); + }); + } + })); + + NewPage defaultPage = pageList.get(0); + + // Check if the mongo escaped widget names are carried to exported file from DB + Layout pageLayout = + DBPages.get(0).getUnpublishedPage().getLayouts().get(0); + Set mongoEscapedWidgets = pageLayout.getMongoEscapedWidgetNames(); + Set expectedMongoEscapedWidgets = Set.of("Table1"); + assertThat(mongoEscapedWidgets).isEqualTo(expectedMongoEscapedWidgets); + + pageLayout = + pageList.get(0).getUnpublishedPage().getLayouts().get(0); + Set exportedMongoEscapedWidgets = pageLayout.getMongoEscapedWidgetNames(); + assertThat(exportedMongoEscapedWidgets).isEqualTo(expectedMongoEscapedWidgets); + + assertThat(exportedApp.getName()).isEqualTo(appName); + assertThat(exportedApp.getWorkspaceId()).isNull(); + assertThat(exportedApp.getPages()).hasSize(1); + assertThat(exportedApp.getPages().get(0).getId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + + assertThat(exportedApp.getPolicies()).isNull(); + + assertThat(pageList).hasSize(1); + assertThat(defaultPage.getApplicationId()).isNull(); + assertThat(defaultPage + .getUnpublishedPage() + .getLayouts() + .get(0) + .getDsl()) + .isNotNull(); + assertThat(defaultPage.getId()).isNull(); + assertThat(defaultPage.getPolicies()).isNull(); + + assertThat(actionList.isEmpty()).isFalse(); + assertThat(actionList).hasSize(3); + NewAction validAction = actionList.stream() + .filter(action -> action.getId().equals("Page1_validAction")) + .findFirst() + .get(); + assertThat(validAction.getApplicationId()).isNull(); + assertThat(validAction.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(validAction.getPluginType()).isEqualTo(PluginType.API); + assertThat(validAction.getWorkspaceId()).isNull(); + assertThat(validAction.getPolicies()).isNull(); + assertThat(validAction.getId()).isNotNull(); + ActionDTO unpublishedAction = validAction.getUnpublishedAction(); + assertThat(unpublishedAction.getPageId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + assertThat(unpublishedAction.getDatasource().getPluginId()) + .isEqualTo(installedPlugin.getPackageName()); + + NewAction testAction1 = actionList.stream() + .filter(action -> + action.getUnpublishedAction().getName().equals("testAction1")) + .findFirst() + .get(); + assertThat(testAction1.getId()).isEqualTo("Page1_testCollection1.testAction1"); + + assertThat(actionCollectionList.isEmpty()).isFalse(); + assertThat(actionCollectionList).hasSize(1); + final ActionCollection actionCollection = actionCollectionList.get(0); + assertThat(actionCollection.getApplicationId()).isNull(); + assertThat(actionCollection.getWorkspaceId()).isNull(); + assertThat(actionCollection.getPolicies()).isNull(); + assertThat(actionCollection.getId()).isNotNull(); + assertThat(actionCollection.getUnpublishedCollection().getPluginType()) + .isEqualTo(PluginType.JS); + assertThat(actionCollection.getUnpublishedCollection().getPageId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + assertThat(actionCollection.getUnpublishedCollection().getPluginId()) + .isEqualTo(installedJsPlugin.getPackageName()); + + assertThat(datasourceList).hasSize(1); + DatasourceStorage datasource = datasourceList.get(0); + assertThat(datasource.getWorkspaceId()).isNull(); + assertThat(datasource.getId()).isNull(); + assertThat(datasource.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(datasource.getDatasourceConfiguration()).isNotNull(); + assertThat(datasource.getDatasourceConfiguration().getAuthentication()) + .isNull(); + assertThat(datasource.getDatasourceConfiguration().getSshProxy()) + .isNull(); + assertThat(datasource.getDatasourceConfiguration().getSshProxyEnabled()) + .isNull(); + assertThat(datasource.getDatasourceConfiguration().getProperties()) + .isNull(); + + assertThat(applicationJson.getInvisibleActionFields()).isNull(); + NewAction validAction2 = actionList.stream() + .filter(action -> action.getId().equals("Page1_validAction2")) + .findFirst() + .get(); + assertEquals(true, validAction2.getUnpublishedAction().getUserSetOnLoad()); + + assertThat(applicationJson.getUnpublishedLayoutmongoEscapedWidgets()) + .isNull(); + assertThat(applicationJson.getPublishedLayoutmongoEscapedWidgets()) + .isNull(); + assertThat(applicationJson.getEditModeTheme()).isNotNull(); + assertThat(applicationJson.getEditModeTheme().isSystemTheme()) + .isTrue(); + assertThat(applicationJson.getEditModeTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + + assertThat(applicationJson.getPublishedTheme()).isNotNull(); + assertThat(applicationJson.getPublishedTheme().isSystemTheme()) + .isTrue(); + assertThat(applicationJson.getPublishedTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + + assertThat(exportedCollectionIds).isNotEmpty(); + assertThat(exportedCollectionIds).doesNotContain(String.valueOf(DBCollectionIds)); + + assertThat(exportedActionIds).isNotEmpty(); + assertThat(exportedActionIds).doesNotContain(String.valueOf(DBActionIds)); + + assertThat(exportedOnLayoutLoadActionIds).isNotEmpty(); + assertThat(exportedOnLayoutLoadActionIds).doesNotContain(String.valueOf(DBOnLayoutLoadActionIds)); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void createExportAppJsonForGitTest() { + + StringBuilder pageName = new StringBuilder(); + final Mono resultMono = applicationRepository + .findById(testAppId) + .flatMap(testApp -> { + final String pageId = testApp.getPages().get(0).getId(); + return Mono.zip(Mono.just(testApp), newPageService.findPageById(pageId, READ_PAGES, false)); + }) + .flatMap(tuple -> { + Datasource ds1 = datasourceMap.get("DS1"); + Application testApp = tuple.getT1(); + PageDTO testPage = tuple.getT2(); + pageName.append(testPage.getName()); + + Layout layout = testPage.getLayouts().get(0); + JSONObject dsl = new JSONObject(Map.of("text", "{{ query1.data }}")); + + layout.setDsl(dsl); + layout.setPublishedDsl(dsl); + + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(testPage.getId()); + action.setExecuteOnLoad(true); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(ds1); + + return layoutActionService + .createAction(action) + .then(exportService.exportByArtifactId( + testApp.getId(), + SerialiseArtifactObjective.VERSION_CONTROL, + ArtifactJsonType.APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + }); + + StepVerifier.create(resultMono) + .assertNext(applicationJson -> { + Application exportedApp = applicationJson.getExportedApplication(); + List pageList = applicationJson.getPageList(); + List actionList = applicationJson.getActionList(); + List datasourceList = applicationJson.getDatasourceList(); + + NewPage newPage = pageList.get(0); + + assertThat(applicationJson.getServerSchemaVersion()).isEqualTo(JsonSchemaVersions.serverVersion); + assertThat(applicationJson.getClientSchemaVersion()).isEqualTo(JsonSchemaVersions.clientVersion); + + assertThat(exportedApp.getName()).isNotNull(); + assertThat(exportedApp.getWorkspaceId()).isNull(); + assertThat(exportedApp.getPages()).hasSize(1); + assertThat(exportedApp.getPages().get(0).getId()).isEqualTo(pageName.toString()); + assertThat(exportedApp.getGitApplicationMetadata()).isNull(); + + assertThat(exportedApp.getApplicationDetail()).isNotNull(); + assertThat(exportedApp.getApplicationDetail().getThemeSetting()) + .isNotNull(); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getSizing()) + .isNotNull(); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getAccentColor()) + .isEqualTo("#FFFFFF"); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getColorMode()) + .isEqualTo(Application.ThemeSetting.Type.LIGHT); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getDensity()) + .isEqualTo(1); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getFontFamily()) + .isEqualTo("#000000"); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getSizing()) + .isEqualTo(1); + assertThat(exportedApp + .getApplicationDetail() + .getThemeSetting() + .getIconStyle()) + .isEqualTo(Application.ThemeSetting.IconStyle.OUTLINED); + + assertThat(exportedApp.getPolicies()).isNull(); + assertThat(exportedApp.getUserPermissions()).isNull(); + + assertThat(pageList).hasSize(1); + assertThat(newPage.getApplicationId()).isNull(); + assertThat(newPage.getUnpublishedPage().getLayouts().get(0).getDsl()) + .isNotNull(); + assertThat(newPage.getId()).isNull(); + assertThat(newPage.getPolicies()).isNull(); + + assertThat(actionList.isEmpty()).isFalse(); + NewAction validAction = actionList.get(0); + assertThat(validAction.getApplicationId()).isNull(); + assertThat(validAction.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(validAction.getPluginType()).isEqualTo(PluginType.API); + assertThat(validAction.getWorkspaceId()).isNull(); + assertThat(validAction.getPolicies()).isNull(); + assertThat(validAction.getId()).isNotNull(); + assertThat(validAction.getUnpublishedAction().getPageId()) + .isEqualTo(newPage.getUnpublishedPage().getName()); + + assertThat(datasourceList).hasSize(1); + DatasourceStorage datasource = datasourceList.get(0); + assertThat(datasource.getWorkspaceId()).isNull(); + assertThat(datasource.getId()).isNull(); + assertThat(datasource.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(datasource.getDatasourceConfiguration()).isNull(); + assertThat(applicationJson.getUnpublishedLayoutmongoEscapedWidgets()) + .isNull(); + assertThat(applicationJson.getPublishedLayoutmongoEscapedWidgets()) + .isNull(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportImportApplication_importWithBranchName_updateApplicationResourcesWithBranch() { + Application testApplication = new Application(); + testApplication.setName("Export-Import-Update-Branch_Test-App"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("testBranch"); + testApplication.setGitApplicationMetadata(gitData); + + Application savedApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + Mono result = newPageService + .findNewPagesByApplicationId(savedApplication.getId(), READ_PAGES) + .collectList() + .flatMap(newPages -> { + NewPage newPage = newPages.get(0); + + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(newPage.getId()); + action.setExecuteOnLoad(true); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasourceMap.get("DS1")); + return layoutActionService + .createAction(action) + .flatMap(createdAction -> newActionService.findById(createdAction.getId(), READ_ACTIONS)); + }) + .then(exportService + .exportByArtifactId( + savedApplication.getId(), + SerialiseArtifactObjective.VERSION_CONTROL, + ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) + .flatMap(applicationJson -> importService.importArtifactInWorkspaceFromGit( + workspaceId, savedApplication.getId(), applicationJson, gitData.getBranchName()))) + .map(importableArtifact -> (Application) importableArtifact) + .cache(); + + Mono> updatedPagesMono = result.then(newPageService + .findNewPagesByApplicationId(savedApplication.getId(), READ_PAGES) + .collectList()); + + Mono> updatedActionsMono = result.then(newActionService + .findAllByApplicationIdAndViewMode(savedApplication.getId(), false, READ_PAGES, null) + .collectList()); + + StepVerifier.create(Mono.zip(result, updatedPagesMono, updatedActionsMono)) + .assertNext(tuple -> { + Application application = tuple.getT1(); + List pageList = tuple.getT2(); + List actionList = tuple.getT3(); + + final String branchName = + application.getGitApplicationMetadata().getBranchName(); + pageList.forEach(page -> { + assertThat(page.getDefaultResources()).isNotNull(); + assertThat(page.getDefaultResources().getBranchName()).isEqualTo(branchName); + }); + + actionList.forEach(action -> { + assertThat(action.getDefaultResources()).isNotNull(); + assertThat(action.getDefaultResources().getBranchName()).isEqualTo(branchName); + }); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importUpdatedApplicationIntoWorkspaceFromFile_publicApplication_visibilityFlagNotReset() { + // Create a application and make it public + // Now add a page and export the same import it to the app + // Check if the policies and visibility flag are not reset + + Mono workspaceResponse = workspaceService.findById(workspaceId, READ_WORKSPACES); + + List permissionGroups = workspaceResponse + .flatMapMany(savedWorkspace -> { + Set defaultPermissionGroups = savedWorkspace.getDefaultPermissionGroups(); + return permissionGroupRepository.findAllById(defaultPermissionGroups); + }) + .collectList() + .block(); + + PermissionGroup adminPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.ADMINISTRATOR)) + .findFirst() + .get(); + + PermissionGroup developerPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.DEVELOPER)) + .findFirst() + .get(); + + PermissionGroup viewerPermissionGroup = permissionGroups.stream() + .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.VIEWER)) + .findFirst() + .get(); + + Application testApplication = new Application(); + testApplication.setName( + "importUpdatedApplicationIntoWorkspaceFromFile_publicApplication_visibilityFlagNotReset"); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setModifiedBy("some-user"); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("master"); + testApplication.setGitApplicationMetadata(gitData); + + Application application = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + ApplicationAccessDTO applicationAccessDTO = new ApplicationAccessDTO(); + applicationAccessDTO.setPublicAccess(true); + Application newApplication = applicationService + .changeViewAccess(application.getId(), "master", applicationAccessDTO) + .block(); + + PermissionGroup anonymousPermissionGroup = + permissionGroupService.getPublicPermissionGroup().block(); + + Policy manageAppPolicy = Policy.builder() + .permission(MANAGE_APPLICATIONS.getValue()) + .permissionGroups(Set.of(adminPermissionGroup.getId(), developerPermissionGroup.getId())) + .build(); + Policy readAppPolicy = Policy.builder() + .permission(READ_APPLICATIONS.getValue()) + .permissionGroups(Set.of( + adminPermissionGroup.getId(), + developerPermissionGroup.getId(), + viewerPermissionGroup.getId(), + anonymousPermissionGroup.getId())) + .build(); + + Mono applicationMono = exportService + .exportByArtifactIdAndBranchName(application.getId(), "master", ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) + .flatMap(applicationJson -> importService.importArtifactInWorkspaceFromGit( + workspaceId, application.getId(), applicationJson, "master")) + .map(importableArtifact -> (Application) importableArtifact); + + StepVerifier.create(applicationMono) + .assertNext(application1 -> { + assertThat(application1.getIsPublic()).isEqualTo(Boolean.TRUE); + assertThat(application1.getPolicies()).containsAll(Set.of(manageAppPolicy, readAppPolicy)); + }) + .verifyComplete(); + } + + @NotNull private static Application.ThemeSetting getThemeSetting() { + Application.ThemeSetting themeSettings = new Application.ThemeSetting(); + themeSettings.setSizing(1); + themeSettings.setDensity(1); + themeSettings.setBorderRadius("#000000"); + themeSettings.setAccentColor("#FFFFFF"); + themeSettings.setFontFamily("#000000"); + themeSettings.setColorMode(Application.ThemeSetting.Type.LIGHT); + themeSettings.setIconStyle(Application.ThemeSetting.IconStyle.OUTLINED); + return themeSettings; + } + + /** + * Testcase to check if the application is exported with the datasource configuration object if this setting is + * enabled from application object + * This can be enabled with exportWithConfiguration: true + */ + @Test + @WithUserDetails(value = "api_user") + public void exportApplication_withDatasourceConfig_exportedWithDecryptedFields() { + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("template-org-with-ds"); + + Application testApplication = new Application(); + testApplication.setName("exportApplication_withCredentialsForSampleApps_SuccessWithDecryptFields"); + testApplication.setExportWithConfiguration(true); + testApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .block(); + assert testApplication != null; + exportWithConfigurationAppId = testApplication.getId(); + ApplicationAccessDTO accessDTO = new ApplicationAccessDTO(); + accessDTO.setPublicAccess(true); + applicationService + .changeViewAccess(exportWithConfigurationAppId, accessDTO) + .block(); + final String appName = testApplication.getName(); + final Mono resultMono = Mono.zip( + Mono.just(testApplication), + newPageService.findPageById( + testApplication.getPages().get(0).getId(), READ_PAGES, false)) + .flatMap(tuple -> { + Application testApp = tuple.getT1(); + PageDTO testPage = tuple.getT2(); + + Layout layout = testPage.getLayouts().get(0); + ObjectMapper objectMapper = new ObjectMapper(); + JSONObject dsl = new JSONObject(); + try { + dsl = new JSONObject(objectMapper.readValue( + DEFAULT_PAGE_LAYOUT, new TypeReference>() {})); + } catch (JsonProcessingException e) { + e.printStackTrace(); + fail(); + } + + ArrayList children = (ArrayList) dsl.get("children"); + JSONObject testWidget = new JSONObject(); + testWidget.put("widgetName", "firstWidget"); + JSONArray temp = new JSONArray(); + temp.add(new JSONObject(Map.of("key", "testField"))); + testWidget.put("dynamicBindingPathList", temp); + testWidget.put("testField", "{{ validAction.data }}"); + children.add(testWidget); + + layout.setDsl(dsl); + layout.setPublishedDsl(dsl); + + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(testPage.getId()); + action.setExecuteOnLoad(true); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasourceMap.get("DS2")); + + ActionDTO action2 = new ActionDTO(); + action2.setName("validAction2"); + action2.setPageId(testPage.getId()); + action2.setExecuteOnLoad(true); + action2.setUserSetOnLoad(true); + ActionConfiguration actionConfiguration2 = new ActionConfiguration(); + actionConfiguration2.setHttpMethod(HttpMethod.GET); + action2.setActionConfiguration(actionConfiguration2); + action2.setDatasource(datasourceMap.get("DS2")); + + ActionCollectionDTO actionCollectionDTO1 = new ActionCollectionDTO(); + actionCollectionDTO1.setName("testCollection1"); + actionCollectionDTO1.setPageId(testPage.getId()); + actionCollectionDTO1.setApplicationId(testApp.getId()); + actionCollectionDTO1.setWorkspaceId(testApp.getWorkspaceId()); + actionCollectionDTO1.setPluginId(jsDatasource.getPluginId()); + ActionDTO action1 = new ActionDTO(); + action1.setName("testAction1"); + action1.setActionConfiguration(new ActionConfiguration()); + action1.getActionConfiguration().setBody("mockBody"); + actionCollectionDTO1.setActions(List.of(action1)); + actionCollectionDTO1.setPluginType(PluginType.JS); + + return layoutCollectionService + .createCollection(actionCollectionDTO1, null) + .then(layoutActionService.createSingleAction(action, Boolean.FALSE)) + .then(layoutActionService.createSingleAction(action2, Boolean.FALSE)) + .then(updateLayoutService.updateLayout( + testPage.getId(), testPage.getApplicationId(), layout.getId(), layout)) + .then(exportService.exportByArtifactIdAndBranchName( + testApp.getId(), "", ArtifactJsonType.APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + }) + .cache(); + + Mono> actionListMono = resultMono.then(newActionService + .findAllByApplicationIdAndViewMode(testApplication.getId(), false, READ_ACTIONS, null) + .collectList()); + + Mono> collectionListMono = resultMono.then(actionCollectionService + .findAllByApplicationIdAndViewMode(testApplication.getId(), false, READ_ACTIONS, null) + .collectList()); + + Mono> pageListMono = resultMono.then(newPageService + .findNewPagesByApplicationId(testApplication.getId(), READ_PAGES) + .collectList()); + + StepVerifier.create(Mono.zip(resultMono, actionListMono, collectionListMono, pageListMono)) + .assertNext(tuple -> { + ApplicationJson applicationJson = tuple.getT1(); + List DBActions = tuple.getT2(); + List DBCollections = tuple.getT3(); + List DBPages = tuple.getT4(); + + Application exportedApp = applicationJson.getExportedApplication(); + List pageList = applicationJson.getPageList(); + List actionList = applicationJson.getActionList(); + List actionCollectionList = applicationJson.getActionCollectionList(); + List datasourceList = applicationJson.getDatasourceList(); + + List exportedCollectionIds = actionCollectionList.stream() + .map(ActionCollection::getId) + .collect(Collectors.toList()); + List exportedActionIds = + actionList.stream().map(NewAction::getId).collect(Collectors.toList()); + List DBCollectionIds = + DBCollections.stream().map(ActionCollection::getId).collect(Collectors.toList()); + List DBActionIds = + DBActions.stream().map(NewAction::getId).collect(Collectors.toList()); + List DBOnLayoutLoadActionIds = new ArrayList<>(); + List exportedOnLayoutLoadActionIds = new ArrayList<>(); + + DBPages.forEach( + newPage -> newPage.getUnpublishedPage().getLayouts().forEach(layout -> { + if (layout.getLayoutOnLoadActions() != null) { + layout.getLayoutOnLoadActions().forEach(dslActionDTOSet -> { + dslActionDTOSet.forEach( + actionDTO -> DBOnLayoutLoadActionIds.add(actionDTO.getId())); + }); + } + })); + + pageList.forEach( + newPage -> newPage.getUnpublishedPage().getLayouts().forEach(layout -> { + if (layout.getLayoutOnLoadActions() != null) { + layout.getLayoutOnLoadActions().forEach(dslActionDTOSet -> { + dslActionDTOSet.forEach( + actionDTO -> exportedOnLayoutLoadActionIds.add(actionDTO.getId())); + }); + } + })); + + NewPage defaultPage = pageList.get(0); + + assertThat(exportedApp.getName()).isEqualTo(appName); + assertThat(exportedApp.getWorkspaceId()).isNull(); + assertThat(exportedApp.getPages()).hasSize(1); + ApplicationPage page = exportedApp.getPages().get(0); + + assertThat(page.getId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + assertThat(page.getIsDefault()).isTrue(); + assertThat(page.getDefaultPageId()).isNull(); + + assertThat(exportedApp.getPolicies()).isNull(); + + assertThat(pageList).hasSize(1); + assertThat(defaultPage.getApplicationId()).isNull(); + assertThat(defaultPage + .getUnpublishedPage() + .getLayouts() + .get(0) + .getDsl()) + .isNotNull(); + assertThat(defaultPage.getId()).isNull(); + assertThat(defaultPage.getPolicies()).isNull(); + + assertThat(actionList.isEmpty()).isFalse(); + assertThat(actionList).hasSize(3); + NewAction validAction = actionList.stream() + .filter(action -> action.getId().equals("Page1_validAction")) + .findFirst() + .get(); + assertThat(validAction.getApplicationId()).isNull(); + assertThat(validAction.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(validAction.getPluginType()).isEqualTo(PluginType.API); + assertThat(validAction.getWorkspaceId()).isNull(); + assertThat(validAction.getPolicies()).isNull(); + assertThat(validAction.getId()).isNotNull(); + ActionDTO unpublishedAction = validAction.getUnpublishedAction(); + assertThat(unpublishedAction.getPageId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + assertThat(unpublishedAction.getDatasource().getPluginId()) + .isEqualTo(installedPlugin.getPackageName()); + + NewAction testAction1 = actionList.stream() + .filter(action -> + action.getUnpublishedAction().getName().equals("testAction1")) + .findFirst() + .get(); + assertThat(testAction1.getId()).isEqualTo("Page1_testCollection1.testAction1"); + + assertThat(actionCollectionList.isEmpty()).isFalse(); + assertThat(actionCollectionList).hasSize(1); + final ActionCollection actionCollection = actionCollectionList.get(0); + assertThat(actionCollection.getApplicationId()).isNull(); + assertThat(actionCollection.getWorkspaceId()).isNull(); + assertThat(actionCollection.getPolicies()).isNull(); + assertThat(actionCollection.getId()).isNotNull(); + assertThat(actionCollection.getUnpublishedCollection().getPluginType()) + .isEqualTo(PluginType.JS); + assertThat(actionCollection.getUnpublishedCollection().getPageId()) + .isEqualTo(defaultPage.getUnpublishedPage().getName()); + assertThat(actionCollection.getUnpublishedCollection().getPluginId()) + .isEqualTo(installedJsPlugin.getPackageName()); + + assertThat(datasourceList).hasSize(1); + DatasourceStorage datasource = datasourceList.get(0); + assertThat(datasource.getWorkspaceId()).isNull(); + assertThat(datasource.getId()).isNull(); + assertThat(datasource.getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(datasource.getDatasourceConfiguration()).isNotNull(); + + final Map invisibleActionFields = + applicationJson.getInvisibleActionFields(); + + assertThat(invisibleActionFields).isNull(); + for (NewAction newAction : actionList) { + if (newAction.getId().equals("Page1_validAction2")) { + assertEquals(true, newAction.getUnpublishedAction().getUserSetOnLoad()); + } else { + assertEquals(false, newAction.getUnpublishedAction().getUserSetOnLoad()); + } + } + + assertThat(applicationJson.getUnpublishedLayoutmongoEscapedWidgets()) + .isNull(); + assertThat(applicationJson.getPublishedLayoutmongoEscapedWidgets()) + .isNull(); + assertThat(applicationJson.getEditModeTheme()).isNotNull(); + assertThat(applicationJson.getEditModeTheme().isSystemTheme()) + .isTrue(); + assertThat(applicationJson.getEditModeTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + + assertThat(applicationJson.getPublishedTheme()).isNotNull(); + assertThat(applicationJson.getPublishedTheme().isSystemTheme()) + .isTrue(); + assertThat(applicationJson.getPublishedTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + + assertThat(exportedCollectionIds).isNotEmpty(); + assertThat(exportedCollectionIds).doesNotContain(String.valueOf(DBCollectionIds)); + + assertThat(exportedActionIds).isNotEmpty(); + assertThat(exportedActionIds).doesNotContain(String.valueOf(DBActionIds)); + + assertThat(exportedOnLayoutLoadActionIds).isNotEmpty(); + assertThat(exportedOnLayoutLoadActionIds).doesNotContain(String.valueOf(DBOnLayoutLoadActionIds)); + + assertThat(applicationJson.getDecryptedFields()).isNotNull(); + }) + .verifyComplete(); + } + + /** + * Test to check if the application can be exported with read only access if this is sample application + */ + @Test + @Disabled + @WithUserDetails(value = "usertest@usertest.com") + public void exportApplication_withReadOnlyAccess_exportedWithDecryptedFields() { + Mono exportApplicationMono = exportService + .exportByArtifactId( + exportWithConfigurationAppId, SerialiseArtifactObjective.SHARE, ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + + StepVerifier.create(exportApplicationMono) + .assertNext(applicationJson -> { + assertThat(applicationJson.getExportedApplication()).isNotNull(); + assertThat(applicationJson.getDecryptedFields()).isNotNull(); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void + exportAndImportApplication_withMultiplePagesOrderSameInDeployAndEditMode_PagesOrderIsMaintainedInEditAndViewMode() { + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("template-org-with-ds"); + + Application testApplication = new Application(); + testApplication.setName( + "exportAndImportApplication_withMultiplePagesOrderSameInDeployAndEditMode_PagesOrderIsMaintainedInEditAndViewMode"); + testApplication.setExportWithConfiguration(true); + testApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .block(); + assert testApplication != null; + + PageDTO testPage1 = new PageDTO(); + testPage1.setName("testPage1"); + testPage1.setApplicationId(testApplication.getId()); + testPage1 = applicationPageService.createPage(testPage1).block(); + + PageDTO testPage2 = new PageDTO(); + testPage2.setName("testPage2"); + testPage2.setApplicationId(testApplication.getId()); + testPage2 = applicationPageService.createPage(testPage2).block(); + + // Set order for the newly created pages + applicationPageService + .reorderPage(testApplication.getId(), testPage1.getId(), 0, null) + .block(); + applicationPageService + .reorderPage(testApplication.getId(), testPage2.getId(), 1, null) + .block(); + // Deploy the current application + applicationPageService.publish(testApplication.getId(), true).block(); + + Mono applicationJsonMono = exportService + .exportByArtifactIdAndBranchName(testApplication.getId(), "", ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) + .cache(); + + StepVerifier.create(applicationJsonMono) + .assertNext(applicationJson -> { + assertThat(applicationJson.getPageOrder()).isNull(); + assertThat(applicationJson.getPublishedPageOrder()).isNull(); + List pageList = applicationJson.getExportedApplication().getPages().stream() + .map(ApplicationPage::getId) + .collect(Collectors.toList()); + + assertThat(pageList.get(0)).isEqualTo("testPage1"); + assertThat(pageList.get(1)).isEqualTo("testPage2"); + assertThat(pageList.get(2)).isEqualTo("Page1"); + + List publishedPageList = + applicationJson.getExportedApplication().getPublishedPages().stream() + .map(ApplicationPage::getId) + .collect(Collectors.toList()); + + assertThat(publishedPageList.get(0)).isEqualTo("testPage1"); + assertThat(publishedPageList.get(1)).isEqualTo("testPage2"); + assertThat(publishedPageList.get(2)).isEqualTo("Page1"); + }) + .verifyComplete(); + + ApplicationJson applicationJson = applicationJsonMono.block(); + Application application = importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact) + .block(); + + // Get the unpublished pages and verify the order + List pageDTOS = application.getPages(); + Mono newPageMono1 = newPageService.findById(pageDTOS.get(0).getId(), MANAGE_PAGES); + Mono newPageMono2 = newPageService.findById(pageDTOS.get(1).getId(), MANAGE_PAGES); + Mono newPageMono3 = newPageService.findById(pageDTOS.get(2).getId(), MANAGE_PAGES); + + StepVerifier.create(Mono.zip(newPageMono1, newPageMono2, newPageMono3)) + .assertNext(objects -> { + NewPage newPage1 = objects.getT1(); + NewPage newPage2 = objects.getT2(); + NewPage newPage3 = objects.getT3(); + assertThat(newPage1.getUnpublishedPage().getName()).isEqualTo("testPage1"); + assertThat(newPage2.getUnpublishedPage().getName()).isEqualTo("testPage2"); + assertThat(newPage3.getUnpublishedPage().getName()).isEqualTo("Page1"); + + assertThat(newPage1.getId()).isEqualTo(pageDTOS.get(0).getId()); + assertThat(newPage2.getId()).isEqualTo(pageDTOS.get(1).getId()); + assertThat(newPage3.getId()).isEqualTo(pageDTOS.get(2).getId()); + }) + .verifyComplete(); + + // Get the published pages + List publishedPageDTOs = application.getPublishedPages(); + Mono newPublishedPageMono1 = + newPageService.findById(publishedPageDTOs.get(0).getId(), MANAGE_PAGES); + Mono newPublishedPageMono2 = + newPageService.findById(publishedPageDTOs.get(1).getId(), MANAGE_PAGES); + Mono newPublishedPageMono3 = + newPageService.findById(publishedPageDTOs.get(2).getId(), MANAGE_PAGES); + + StepVerifier.create(Mono.zip(newPublishedPageMono1, newPublishedPageMono2, newPublishedPageMono3)) + .assertNext(objects -> { + NewPage newPage1 = objects.getT1(); + NewPage newPage2 = objects.getT2(); + NewPage newPage3 = objects.getT3(); + assertThat(newPage1.getPublishedPage().getName()).isEqualTo("testPage1"); + assertThat(newPage2.getPublishedPage().getName()).isEqualTo("testPage2"); + assertThat(newPage3.getPublishedPage().getName()).isEqualTo("Page1"); + + assertThat(newPage1.getId()) + .isEqualTo(publishedPageDTOs.get(0).getId()); + assertThat(newPage2.getId()) + .isEqualTo(publishedPageDTOs.get(1).getId()); + assertThat(newPage3.getId()) + .isEqualTo(publishedPageDTOs.get(2).getId()); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void + importApplicationInWorkspaceFromGit_WithNavSettingsInEditMode_ImportedAppHasNavSettingsInEditAndViewMode() { + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("import-with-navSettings-in-editMode"); + + Application testApplication = new Application(); + testApplication.setName( + "importApplicationInWorkspaceFromGit_WithNavSettingsInEditMode_ImportedAppHasNavSettingsInEditAndViewMode"); + Application.NavigationSetting appNavigationSetting = new Application.NavigationSetting(); + appNavigationSetting.setOrientation("top"); + testApplication.setUnpublishedApplicationDetail(new ApplicationDetail()); + testApplication.getUnpublishedApplicationDetail().setNavigationSetting(appNavigationSetting); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("testBranch"); + testApplication.setGitApplicationMetadata(gitData); + Application savedApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + Mono result = exportService + .exportByArtifactId( + savedApplication.getId(), + SerialiseArtifactObjective.VERSION_CONTROL, + ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) + .flatMap(applicationJson -> { + // setting published mode resource as null, similar to the app json exported to git repo + applicationJson.getExportedApplication().setPublishedApplicationDetail(null); + return importService + .importArtifactInWorkspaceFromGit( + workspaceId, savedApplication.getId(), applicationJson, gitData.getBranchName()) + .map(importableArtifact -> (Application) importableArtifact); + }); + + StepVerifier.create(result) + .assertNext(importedApp -> { + assertThat(importedApp.getUnpublishedApplicationDetail()).isNotNull(); + assertThat(importedApp.getPublishedApplicationDetail()).isNotNull(); + assertThat(importedApp.getUnpublishedApplicationDetail().getNavigationSetting()) + .isNotNull(); + assertEquals( + importedApp + .getUnpublishedApplicationDetail() + .getNavigationSetting() + .getOrientation(), + "top"); + assertThat(importedApp.getPublishedApplicationDetail().getNavigationSetting()) + .isNotNull(); + assertEquals( + importedApp + .getPublishedApplicationDetail() + .getNavigationSetting() + .getOrientation(), + "top"); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void importApplicationInWorkspaceFromGit_WithAppLayoutInEditMode_ImportedAppHasAppLayoutInEditAndViewMode() { + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("import-with-appLayout-in-editMode"); + + Application testApplication = new Application(); + testApplication.setName( + "importApplicationInWorkspaceFromGit_WithAppLayoutInEditMode_ImportedAppHasAppLayoutInEditAndViewMode"); + testApplication.setUnpublishedAppLayout(new Application.AppLayout(Application.AppLayout.Type.DESKTOP)); + testApplication.setGitApplicationMetadata(new GitApplicationMetadata()); + GitApplicationMetadata gitData = new GitApplicationMetadata(); + gitData.setBranchName("testBranch"); + testApplication.setGitApplicationMetadata(gitData); + Application savedApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application1 -> { + application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId()); + return applicationService.save(application1); + }) + .block(); + + Mono result = exportService + .exportByArtifactId( + savedApplication.getId(), + SerialiseArtifactObjective.VERSION_CONTROL, + ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) + .flatMap(applicationJson -> { + // setting published mode resource as null, similar to the app json exported to git repo + applicationJson.getExportedApplication().setPublishedAppLayout(null); + return importService + .importArtifactInWorkspaceFromGit( + workspaceId, savedApplication.getId(), applicationJson, gitData.getBranchName()) + .map(importableArtifact -> (Application) importableArtifact); + }); + + StepVerifier.create(result) + .assertNext(importedApp -> { + assertThat(importedApp.getUnpublishedAppLayout()).isNotNull(); + assertThat(importedApp.getPublishedAppLayout()).isNotNull(); + assertThat(importedApp.getUnpublishedAppLayout().getType()) + .isEqualTo(Application.AppLayout.Type.DESKTOP); + assertThat(importedApp.getPublishedAppLayout().getType()) + .isEqualTo(Application.AppLayout.Type.DESKTOP); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void + exportAndImportApplication_withMultiplePagesOrderDifferentInDeployAndEditMode_PagesOrderIsMaintainedInEditAndViewMode() { + Workspace newWorkspace = new Workspace(); + newWorkspace.setName("template-org-with-ds"); + + Application testApplication = new Application(); + testApplication.setName( + "exportAndImportApplication_withMultiplePagesOrderDifferentInDeployAndEditMode_PagesOrderIsMaintainedInEditAndViewMode"); + testApplication.setExportWithConfiguration(true); + testApplication = applicationPageService + .createApplication(testApplication, workspaceId) + .block(); + assert testApplication != null; + + PageDTO testPage1 = new PageDTO(); + testPage1.setName("testPage1"); + testPage1.setApplicationId(testApplication.getId()); + testPage1 = applicationPageService.createPage(testPage1).block(); + + PageDTO testPage2 = new PageDTO(); + testPage2.setName("testPage2"); + testPage2.setApplicationId(testApplication.getId()); + testPage2 = applicationPageService.createPage(testPage2).block(); + + // Deploy the current application so that edit and view mode will have different page order + applicationPageService.publish(testApplication.getId(), true).block(); + + // Set order for the newly created pages + applicationPageService + .reorderPage(testApplication.getId(), testPage1.getId(), 0, null) + .block(); + applicationPageService + .reorderPage(testApplication.getId(), testPage2.getId(), 1, null) + .block(); + + Mono applicationJsonMono = exportService + .exportByArtifactIdAndBranchName(testApplication.getId(), "", ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) + .cache(); + + StepVerifier.create(applicationJsonMono) + .assertNext(applicationJson -> { + Application exportedApplication = applicationJson.getExportedApplication(); + exportedApplication.setViewMode(false); + List pageOrder = exportedApplication.getPages().stream() + .map(ApplicationPage::getId) + .collect(Collectors.toList()); + assertThat(pageOrder.get(0)).isEqualTo("testPage1"); + assertThat(pageOrder.get(1)).isEqualTo("testPage2"); + assertThat(pageOrder.get(2)).isEqualTo("Page1"); + + pageOrder.clear(); + pageOrder = exportedApplication.getPublishedPages().stream() + .map(ApplicationPage::getId) + .collect(Collectors.toList()); + assertThat(pageOrder.get(0)).isEqualTo("Page1"); + assertThat(pageOrder.get(1)).isEqualTo("testPage1"); + assertThat(pageOrder.get(2)).isEqualTo("testPage2"); + }) + .verifyComplete(); + + ApplicationJson applicationJson = applicationJsonMono.block(); + Application application = importService + .importNewArtifactInWorkspaceFromJson(workspaceId, applicationJson) + .map(importableArtifact -> (Application) importableArtifact) + .block(); + + // Get the unpublished pages and verify the order + application.setViewMode(false); + List pageDTOS = application.getPages(); + Mono newPageMono1 = newPageService.findById(pageDTOS.get(0).getId(), MANAGE_PAGES); + Mono newPageMono2 = newPageService.findById(pageDTOS.get(1).getId(), MANAGE_PAGES); + Mono newPageMono3 = newPageService.findById(pageDTOS.get(2).getId(), MANAGE_PAGES); + + StepVerifier.create(Mono.zip(newPageMono1, newPageMono2, newPageMono3)) + .assertNext(objects -> { + NewPage newPage1 = objects.getT1(); + NewPage newPage2 = objects.getT2(); + NewPage newPage3 = objects.getT3(); + assertThat(newPage1.getUnpublishedPage().getName()).isEqualTo("testPage1"); + assertThat(newPage2.getUnpublishedPage().getName()).isEqualTo("testPage2"); + assertThat(newPage3.getUnpublishedPage().getName()).isEqualTo("Page1"); + + assertThat(newPage1.getId()).isEqualTo(pageDTOS.get(0).getId()); + assertThat(newPage2.getId()).isEqualTo(pageDTOS.get(1).getId()); + assertThat(newPage3.getId()).isEqualTo(pageDTOS.get(2).getId()); + }) + .verifyComplete(); + + // Get the published pages + List publishedPageDTOs = application.getPublishedPages(); + Mono newPublishedPageMono1 = + newPageService.findById(publishedPageDTOs.get(0).getId(), MANAGE_PAGES); + Mono newPublishedPageMono2 = + newPageService.findById(publishedPageDTOs.get(1).getId(), MANAGE_PAGES); + Mono newPublishedPageMono3 = + newPageService.findById(publishedPageDTOs.get(2).getId(), MANAGE_PAGES); + + StepVerifier.create(Mono.zip(newPublishedPageMono1, newPublishedPageMono2, newPublishedPageMono3)) + .assertNext(objects -> { + NewPage newPage1 = objects.getT1(); + NewPage newPage2 = objects.getT2(); + NewPage newPage3 = objects.getT3(); + assertThat(newPage1.getPublishedPage().getName()).isEqualTo("Page1"); + assertThat(newPage2.getPublishedPage().getName()).isEqualTo("testPage1"); + assertThat(newPage3.getPublishedPage().getName()).isEqualTo("testPage2"); + + assertThat(newPage1.getId()) + .isEqualTo(publishedPageDTOs.get(0).getId()); + assertThat(newPage2.getId()) + .isEqualTo(publishedPageDTOs.get(1).getId()); + assertThat(newPage3.getId()) + .isEqualTo(publishedPageDTOs.get(2).getId()); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplicationById_WhenThemeDoesNotExist_ExportedWithDefaultTheme() { + Theme customTheme = new Theme(); + customTheme.setName("my-custom-theme"); + + String randomId = UUID.randomUUID().toString(); + Application testApplication = new Application(); + testApplication.setName("Application_" + randomId); + Mono exportedAppJson = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application -> { + application.setEditModeThemeId("invalid-theme-id"); + application.setPublishedModeThemeId("invalid-theme-id"); + String branchName = null; + return applicationService + .save(application) + .then(exportService.exportByArtifactIdAndBranchName( + application.getId(), branchName, ArtifactJsonType.APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + }); + + StepVerifier.create(exportedAppJson) + .assertNext(applicationJson -> { + assertThat(applicationJson.getEditModeTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + assertThat(applicationJson.getPublishedTheme().getName()) + .isEqualToIgnoringCase(Theme.DEFAULT_THEME_NAME); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplication_WithBearerTokenAndExportWithConfig_exportedWithDecryptedFields() { + String randomUUID = UUID.randomUUID().toString(); + + Workspace testWorkspace = new Workspace(); + testWorkspace.setName("workspace-" + randomUUID); + Workspace workspace = workspaceService.create(testWorkspace).block(); + + Application testApplication = new Application(); + testApplication.setName("application-" + randomUUID); + testApplication.setExportWithConfiguration(true); + testApplication.setWorkspaceId(workspace.getId()); + + Mono applicationMono = applicationPageService + .createApplication(testApplication) + .flatMap(application -> { + ApplicationAccessDTO accessDTO = new ApplicationAccessDTO(); + accessDTO.setPublicAccess(true); + return applicationService + .changeViewAccess(application.getId(), accessDTO) + .thenReturn(application); + }); + + Mono datasourceMono = workspaceService + .getDefaultEnvironmentId(workspace.getId(), environmentPermission.getExecutePermission()) + .zipWith(pluginRepository.findByPackageName("restapi-plugin")) + .flatMap(objects -> { + String defaultEnvironmentId = objects.getT1(); + Plugin plugin = objects.getT2(); + + Datasource datasource = new Datasource(); + datasource.setPluginId(plugin.getId()); + datasource.setName("RestAPIWithBearerToken"); + datasource.setWorkspaceId(workspace.getId()); + datasource.setIsConfigured(true); + + BearerTokenAuth bearerTokenAuth = new BearerTokenAuth(); + bearerTokenAuth.setBearerToken("token_" + randomUUID); + + SSLDetails sslDetails = new SSLDetails(); + sslDetails.setAuthType(SSLDetails.AuthType.DEFAULT); + Connection connection = new Connection(); + connection.setSsl(sslDetails); + + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(bearerTokenAuth); + datasourceConfiguration.setConnection(connection); + datasourceConfiguration.setConnection(new Connection()); + datasourceConfiguration.setUrl("https://mock-api.appsmith.com"); + + HashMap storages = new HashMap<>(); + storages.put( + defaultEnvironmentId, + new DatasourceStorageDTO(null, defaultEnvironmentId, datasourceConfiguration)); + datasource.setDatasourceStorages(storages); + + return datasourceService.create(datasource); + }); + + Mono exportAppMono = Mono.zip(applicationMono, datasourceMono) + .flatMap(objects -> { + ApplicationPage applicationPage = objects.getT1().getPages().get(0); + ActionDTO action = new ActionDTO(); + action.setName("validAction"); + action.setPageId(applicationPage.getId()); + action.setPluginId(objects.getT2().getPluginId()); + action.setDatasource(objects.getT2()); + + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + actionConfiguration.setPath("/test/path"); + action.setActionConfiguration(actionConfiguration); + + return layoutActionService + .createSingleAction(action, Boolean.FALSE) + .then(exportService.exportByArtifactIdAndBranchName( + objects.getT1().getId(), "", ArtifactJsonType.APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + }); + + StepVerifier.create(exportAppMono) + .assertNext(applicationJson -> { + assertThat(applicationJson.getDecryptedFields()).isNotEmpty(); + DecryptedSensitiveFields fields = + applicationJson.getDecryptedFields().get("RestAPIWithBearerToken"); + assertThat(fields.getBearerTokenAuth().getBearerToken()).isEqualTo("token_" + randomUUID); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplicationTest_WithNavigationSettings() { + + Application application = new Application(); + application.setName("exportNavigationSettingsApplicationTest"); + Application.NavigationSetting navSetting = new Application.NavigationSetting(); + navSetting.setOrientation("top"); + application.setUnpublishedApplicationDetail(new ApplicationDetail()); + application.getUnpublishedApplicationDetail().setNavigationSetting(navSetting); + Application createdApplication = applicationPageService + .createApplication(application, workspaceId) + .block(); + + Mono resultMono = exportService + .exportByArtifactIdAndBranchName(createdApplication.getId(), "", ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + + StepVerifier.create(resultMono) + .assertNext(applicationJson -> { + Application exportedApplication = applicationJson.getExportedApplication(); + assertThat(exportedApplication).isNotNull(); + assertThat(exportedApplication + .getUnpublishedApplicationDetail() + .getNavigationSetting()) + .isNotNull(); + assertThat(exportedApplication + .getUnpublishedApplicationDetail() + .getNavigationSetting() + .getOrientation()) + .isEqualTo("top"); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void exportApplication_WithPageIcon_ValidPageIcon() { + String randomId = UUID.randomUUID().toString(); + Application application = new Application(); + application.setName("exportPageIconApplicationTest"); + Application createdApplication = applicationPageService + .createApplication(application, workspaceId) + .block(); + + PageDTO pageDTO = new PageDTO(); + pageDTO.setName("page_" + randomId); + pageDTO.setIcon("flight"); + pageDTO.setApplicationId(createdApplication.getId()); + + PageDTO applicationPageDTO = applicationPageService.createPage(pageDTO).block(); + + Mono resultMono = exportService + .exportByArtifactIdAndBranchName( + applicationPageDTO.getApplicationId(), "", ArtifactJsonType.APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + + StepVerifier.create(resultMono) + .assertNext(applicationJson -> { + List pages = applicationJson.getPageList(); + assertThat(pages.size()).isEqualTo(2); + assertThat(pages.get(1).getUnpublishedPage().getName()).isEqualTo("page_" + randomId); + assertThat(pages.get(1).getUnpublishedPage().getIcon()).isEqualTo("flight"); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void createExportAppJsonWithCustomJSLibTest() { + CustomJSLib jsLib = new CustomJSLib("TestLib", Set.of("accessor1"), "url", "docsUrl", "1.0", "defs_string"); + Mono addJSLibMonoCached = customJSLibService + .addJSLibsToContext(testAppId, CreatorContextType.APPLICATION, Set.of(jsLib), null, false) + .flatMap(isJSLibAdded -> + Mono.zip(Mono.just(isJSLibAdded), applicationPageService.publish(testAppId, true))) + .map(tuple2 -> { + Boolean isJSLibAdded = tuple2.getT1(); + Application application = tuple2.getT2(); + return isJSLibAdded; + }) + .cache(); + Mono getExportedAppMono = addJSLibMonoCached + .then(exportService.exportByArtifactIdAndBranchName(testAppId, "", ArtifactJsonType.APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + + StepVerifier.create(Mono.zip(addJSLibMonoCached, getExportedAppMono)) + .assertNext(tuple2 -> { + Boolean isJSLibAdded = tuple2.getT1(); + assertEquals(true, isJSLibAdded); + ApplicationJson exportedAppJson = tuple2.getT2(); + assertEquals(1, exportedAppJson.getCustomJSLibList().size()); + CustomJSLib exportedJSLib = + exportedAppJson.getCustomJSLibList().get(0); + assertEquals(jsLib.getName(), exportedJSLib.getName()); + assertEquals(jsLib.getAccessor(), exportedJSLib.getAccessor()); + assertEquals(jsLib.getUrl(), exportedJSLib.getUrl()); + assertEquals(jsLib.getDocsUrl(), exportedJSLib.getDocsUrl()); + assertEquals(jsLib.getVersion(), exportedJSLib.getVersion()); + assertEquals(jsLib.getDefs(), exportedJSLib.getDefs()); + assertEquals( + getDTOFromCustomJSLib(jsLib), + exportedAppJson + .getExportedApplication() + .getUnpublishedCustomJSLibs() + .toArray()[0]); + assertEquals( + 1, + exportedAppJson + .getExportedApplication() + .getUnpublishedCustomJSLibs() + .size()); + assertEquals( + 0, + exportedAppJson + .getExportedApplication() + .getPublishedCustomJSLibs() + .size()); + }) + .verifyComplete(); + } + + private Mono createActionToPage(String actionName, String pageId) { + ActionDTO action = new ActionDTO(); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasourceMap.get("DS1")); + action.setName(actionName); + action.setPageId(pageId); + return layoutActionService.createAction(action); + } + + private Mono createActionCollectionToPage(Application application, int pageIndex) { + ActionCollectionDTO actionCollectionDTO1 = new ActionCollectionDTO(); + actionCollectionDTO1.setName("TestJsObject"); + actionCollectionDTO1.setPageId(application.getPages().get(pageIndex).getId()); + actionCollectionDTO1.setApplicationId(application.getId()); + actionCollectionDTO1.setWorkspaceId(application.getWorkspaceId()); + actionCollectionDTO1.setPluginId(jsDatasource.getPluginId()); + ActionDTO action1 = new ActionDTO(); + action1.setName("testMethod"); + action1.setActionConfiguration(new ActionConfiguration()); + action1.getActionConfiguration().setBody("mockBody"); + actionCollectionDTO1.setActions(List.of(action1)); + actionCollectionDTO1.setPluginType(PluginType.JS); + return layoutCollectionService.createCollection(actionCollectionDTO1, null); + } + + @Test + @WithUserDetails("api_user") + public void exportApplicationByWhen_WhenGitConnectedAndPageRenamed_QueriesAreInUpdatedResources() { + String renamedPageName = "Renamed Page"; + // create an application + Application testApplication = new Application(); + final String appName = UUID.randomUUID().toString(); + testApplication.setName(appName); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setClientSchemaVersion(JsonSchemaVersions.clientVersion); + testApplication.setServerSchemaVersion(JsonSchemaVersions.serverVersion); + + Mono applicationJsonMono = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application -> { + // add another page to the application + PageDTO pageDTO = new PageDTO(); + pageDTO.setName("second_page"); + pageDTO.setApplicationId(application.getId()); + return applicationPageService + .createPage(pageDTO) // get the updated application + .then(applicationService.findById(application.getId())); + }) + .flatMap(application -> { + assert application.getPages().size() == 2; + // add one action to each of the pages + return createActionToPage( + "first_page_action", + application.getPages().get(0).getId()) + .then(createActionToPage( + "second_page_action", + application.getPages().get(1).getId())) + .thenReturn(application); + }) + .flatMap(application -> { + // add one action collection to each of the pages + return createActionCollectionToPage(application, 0) + .then(createActionCollectionToPage(application, 1)) + .thenReturn(application); + }) + .flatMap(application -> { + // set git meta data for the application and set a last commit date + GitApplicationMetadata gitApplicationMetadata = new GitApplicationMetadata(); + // add buffer of 5 seconds so that the last commit date is definitely after the last updated date + gitApplicationMetadata.setLastCommittedAt(Instant.now()); + application.setGitApplicationMetadata(gitApplicationMetadata); + return applicationRepository.save(application); + }) + .delayElement(Duration.ofMillis( + 100)) // to make sure the last commit date is definitely after the last updated date + .flatMap(application -> { + // rename the page + ApplicationPage applicationPage = application.getPages().get(0); + PageDTO pageDTO = new PageDTO(); + pageDTO.setName(renamedPageName); + return newPageService + .updatePage(applicationPage.getId(), pageDTO) + // export the application + .then(exportService.exportByArtifactId( + application.getId(), + SerialiseArtifactObjective.VERSION_CONTROL, + ArtifactJsonType.APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + }); + + // verify that the exported json has the updated page name, and the queries are in the updated resources + StepVerifier.create(applicationJsonMono) + .assertNext(applicationJson -> { + Map> updatedResources = applicationJson.getUpdatedResources(); + assertThat(updatedResources).isNotNull(); + Set updatedPageNames = updatedResources.get(FieldName.PAGE_LIST); + Set updatedActionNames = updatedResources.get(FieldName.ACTION_LIST); + Set updatedActionCollectionNames = updatedResources.get(FieldName.ACTION_COLLECTION_LIST); + + assertThat(updatedPageNames).isNotNull(); + assertThat(updatedActionNames).isNotNull(); + assertThat(updatedActionCollectionNames).isNotNull(); + + // only the first page should be present in the updated resources + assertThat(updatedPageNames.size()).isEqualTo(1); + assertThat(updatedPageNames).contains(renamedPageName); + + // only actions from first page should be present in the updated resources + // 1 query + 1 method from action collection + assertThat(updatedActionNames.size()).isEqualTo(2); + assertThat(updatedActionNames).contains("first_page_action" + NAME_SEPARATOR + renamedPageName); + assertThat(updatedActionNames) + .contains("TestJsObject.testMethod" + NAME_SEPARATOR + renamedPageName); + + // only action collections from first page should be present in the updated resources + assertThat(updatedActionCollectionNames.size()).isEqualTo(1); + assertThat(updatedActionCollectionNames) + .contains("TestJsObject" + NAME_SEPARATOR + renamedPageName); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails("api_user") + public void exportApplicationByWhen_WhenGitConnectedAndDatasourceRenamed_QueriesAreInUpdatedResources() { + // create an application + Application testApplication = new Application(); + final String appName = UUID.randomUUID().toString(); + testApplication.setName(appName); + testApplication.setWorkspaceId(workspaceId); + testApplication.setUpdatedAt(Instant.now()); + testApplication.setLastDeployedAt(Instant.now()); + testApplication.setClientSchemaVersion(JsonSchemaVersions.clientVersion); + testApplication.setServerSchemaVersion(JsonSchemaVersions.serverVersion); + + Mono applicationJsonMono = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application -> { + // add a datasource to the workspace + Datasource ds1 = new Datasource(); + ds1.setName("DS_FOR_RENAME_TEST"); + ds1.setWorkspaceId(workspaceId); + ds1.setPluginId(installedPlugin.getId()); + final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setUrl("http://example.org/get"); + datasourceConfiguration.setHeaders(List.of(new Property("X-Answer", "42"))); + + HashMap storages1 = new HashMap<>(); + storages1.put( + defaultEnvironmentId, + new DatasourceStorageDTO(null, defaultEnvironmentId, datasourceConfiguration)); + ds1.setDatasourceStorages(storages1); + return datasourceService.create(ds1).zipWith(Mono.just(application)); + }) + .flatMap(objects -> { + Datasource datasource = objects.getT1(); + Application application = objects.getT2(); + + // create an action with the datasource + ActionDTO action = new ActionDTO(); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasource); + action.setName("MyAction"); + action.setPageId(application.getPages().get(0).getId()); + return layoutActionService.createAction(action).thenReturn(objects); + }) + .flatMap(objects -> { + Application application = objects.getT2(); + // set git meta data for the application and set a last commit date + GitApplicationMetadata gitApplicationMetadata = new GitApplicationMetadata(); + // add buffer of 5 seconds so that the last commit date is definitely after the last updated date + gitApplicationMetadata.setLastCommittedAt(Instant.now()); + application.setGitApplicationMetadata(gitApplicationMetadata); + return applicationRepository.save(application).thenReturn(objects); + }) + .delayElement(Duration.ofMillis( + 100)) // to make sure the last commit date is definitely after the last updated date + .flatMap(objects -> { + // rename the datasource + Datasource datasource = objects.getT1(); + Application application = objects.getT2(); + datasource.setName("DS_FOR_RENAME_TEST_RENAMED"); + return datasourceService + .save(datasource) + .then(exportService.exportByArtifactId( + application.getId(), + SerialiseArtifactObjective.VERSION_CONTROL, + ArtifactJsonType.APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); + }); + + // verify that the exported json has the updated page name, and the queries are in the updated resources + StepVerifier.create(applicationJsonMono) + .assertNext(applicationJson -> { + Map> updatedResources = applicationJson.getUpdatedResources(); + assertThat(updatedResources).isNotNull(); + Set updatedActionNames = updatedResources.get(FieldName.ACTION_LIST); + assertThat(updatedActionNames).isNotNull(); + + // action should be present in the updated resources although action not updated but datasource is + assertThat(updatedActionNames.size()).isEqualTo(1); + updatedActionNames.forEach(actionName -> { + assertThat(actionName).contains("MyAction"); + }); + }) + .verifyComplete(); + } +} diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/imports/internal/ImportServiceTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/imports/internal/ImportServiceTests.java index c2c64b193c..9c206cd8cf 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/imports/internal/ImportServiceTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/imports/internal/ImportServiceTests.java @@ -19,9 +19,7 @@ import com.appsmith.external.models.Property; import com.appsmith.external.models.SSLDetails; import com.appsmith.server.actioncollections.base.ActionCollectionService; import com.appsmith.server.applications.base.ApplicationService; -import com.appsmith.server.constants.ArtifactJsonType; import com.appsmith.server.constants.FieldName; -import com.appsmith.server.constants.SerialiseApplicationObjective; import com.appsmith.server.datasources.base.DatasourceService; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; @@ -48,7 +46,7 @@ import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.dtos.PageNameIdDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; -import com.appsmith.server.exports.internal.ExportApplicationService; +import com.appsmith.server.exports.exportable.ExportService; import com.appsmith.server.helpers.MockPluginExecutor; import com.appsmith.server.helpers.PluginExecutorHelper; import com.appsmith.server.imports.importable.ImportService; @@ -73,7 +71,6 @@ import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.WorkspaceService; import com.appsmith.server.solutions.ApplicationPermission; import com.appsmith.server.solutions.EnvironmentPermission; -import com.appsmith.server.solutions.PagePermission; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -133,6 +130,8 @@ import static com.appsmith.server.acl.AclPermission.READ_ACTIONS; import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS; import static com.appsmith.server.acl.AclPermission.READ_PAGES; import static com.appsmith.server.acl.AclPermission.READ_WORKSPACES; +import static com.appsmith.server.constants.ArtifactJsonType.APPLICATION; +import static com.appsmith.server.constants.SerialiseArtifactObjective.VERSION_CONTROL; import static com.appsmith.server.constants.ce.FieldNameCE.DEFAULT_PAGE_LAYOUT; import static com.appsmith.server.dtos.ce.CustomJSLibContextCE_DTO.getDTOFromCustomJSLib; import static org.assertj.core.api.Assertions.assertThat; @@ -145,8 +144,6 @@ import static org.junit.jupiter.api.Assertions.fail; @DirtiesContext @TestMethodOrder(MethodOrderer.MethodName.class) public class ImportServiceTests { - - private static final String INVALID_JSON_FILE = "invalid json file"; private static final Map datasourceMap = new HashMap<>(); private static Plugin installedPlugin; private static String workspaceId; @@ -161,10 +158,10 @@ public class ImportServiceTests { ImportService importService; @Autowired - ExportApplicationService exportApplicationService; + ExportService exportService; // @Autowired - // ImportApplicationService importApplicationService; + // ExportApplicationService exportApplicationService; @Autowired Gson gson; @@ -223,9 +220,6 @@ public class ImportServiceTests { @Autowired EnvironmentPermission environmentPermission; - @Autowired - PagePermission pagePermission; - @Autowired ApplicationPermission applicationPermission; @@ -372,7 +366,9 @@ public class ImportServiceTests { @Test @WithUserDetails(value = "api_user") public void exportApplicationById_WhenContainsInternalFields_InternalFieldsNotExported() { - Mono resultMono = exportApplicationService.exportApplicationById(testAppId, ""); + Mono resultMono = exportService + .exportByArtifactIdAndBranchName(testAppId, "", APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); StepVerifier.create(resultMono) .assertNext(applicationJson -> { @@ -409,7 +405,9 @@ public class ImportServiceTests { return applicationPageService.createApplication(testApplication, workspaceId); }) - .flatMap(application -> exportApplicationService.exportApplicationById(application.getId(), "")); + .flatMap(application -> + exportService.exportByArtifactIdAndBranchName(application.getId(), "", APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); StepVerifier.create(resultMono) .assertNext(applicationJson -> { @@ -517,7 +515,8 @@ public class ImportServiceTests { .then(layoutActionService.createSingleAction(action2, Boolean.FALSE)) .then(updateLayoutService.updateLayout( testPage.getId(), testPage.getApplicationId(), layout.getId(), layout)) - .then(exportApplicationService.exportApplicationById(testApp.getId(), "")); + .then(exportService.exportByArtifactIdAndBranchName(testApp.getId(), "", APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); }) .cache(); @@ -734,8 +733,8 @@ public class ImportServiceTests { return layoutActionService .createAction(action) - .then(exportApplicationService.exportApplicationById( - testApp.getId(), SerialiseApplicationObjective.VERSION_CONTROL)); + .then(exportService.exportByArtifactId(testApp.getId(), VERSION_CONTROL, APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); }); StepVerifier.create(resultMono) @@ -838,8 +837,8 @@ public class ImportServiceTests { Mockito.when(filepart.content()).thenReturn(dataBufferFlux); Mockito.when(filepart.headers().getContentType()).thenReturn(MediaType.IMAGE_PNG); - Mono resultMono = importService.extractArtifactExchangeJsonAndSaveArtifact( - filepart, workspaceId, null, ArtifactJsonType.APPLICATION); + Mono resultMono = + importService.extractArtifactExchangeJsonAndSaveArtifact(filepart, workspaceId, null, APPLICATION); StepVerifier.create(resultMono) .expectErrorMatches(error -> error instanceof AppsmithException) @@ -851,8 +850,8 @@ public class ImportServiceTests { public void importArtifactWithNullWorkspaceIdTest() { FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS); - Mono resultMono = importService.extractArtifactExchangeJsonAndSaveArtifact( - filepart, null, null, ArtifactJsonType.APPLICATION); + Mono resultMono = + importService.extractArtifactExchangeJsonAndSaveArtifact(filepart, null, null, APPLICATION); StepVerifier.create(resultMono) .expectErrorMatches(throwable -> throwable instanceof AppsmithException @@ -868,8 +867,8 @@ public class ImportServiceTests { FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/invalid-json-without-pages.json"); - Mono resultMono = importService.extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspaceId, null, ArtifactJsonType.APPLICATION); + Mono resultMono = + importService.extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, null, APPLICATION); StepVerifier.create(resultMono) .expectErrorMatches(throwable -> throwable instanceof AppsmithException @@ -885,8 +884,8 @@ public class ImportServiceTests { public void importArtifactFromInvalidJsonFileWithoutArtifactTest() { FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/invalid-json-without-app.json"); - Mono resultMono = importService.extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspaceId, null, ArtifactJsonType.APPLICATION); + Mono resultMono = + importService.extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, null, APPLICATION); StepVerifier.create(resultMono) .expectErrorMatches( @@ -918,7 +917,7 @@ public class ImportServiceTests { final Mono resultMono = workspaceMono.flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)); + filePart, workspace.getId(), null, APPLICATION)); List permissionGroups = workspaceMono .flatMapMany(savedWorkspace -> { @@ -1092,8 +1091,7 @@ public class ImportServiceTests { newWorkspace = workspaceService.create(newWorkspace).block(); importService - .extractArtifactExchangeJsonAndSaveArtifact( - filePart, newWorkspace.getId(), null, ArtifactJsonType.APPLICATION) + .extractArtifactExchangeJsonAndSaveArtifact(filePart, newWorkspace.getId(), null, APPLICATION) .timeout(Duration.ofMillis(10)) .subscribe(); @@ -1130,7 +1128,7 @@ public class ImportServiceTests { final Mono resultMono = workspaceService .create(newWorkspace) .flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)) + filePart, workspace.getId(), null, APPLICATION)) .map(artifactImportDTO -> (ApplicationImportDTO) artifactImportDTO); StepVerifier.create(resultMono.flatMap(applicationImportDTO -> Mono.zip( @@ -1171,7 +1169,7 @@ public class ImportServiceTests { final Mono resultMono = workspaceMono .flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)) + filePart, workspace.getId(), null, APPLICATION)) .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); List permissionGroups = workspaceMono @@ -1289,7 +1287,7 @@ public class ImportServiceTests { final Mono resultMono = workspaceService .create(newWorkspace) .flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)) + filePart, workspace.getId(), null, APPLICATION)) .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); StepVerifier.create(resultMono) @@ -1315,7 +1313,7 @@ public class ImportServiceTests { final Mono resultMono = workspaceService .create(newWorkspace) .flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)) + filePart, workspace.getId(), null, APPLICATION)) .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); StepVerifier.create(resultMono.flatMap(applicationImportDTO -> Mono.zip( @@ -1396,8 +1394,9 @@ public class ImportServiceTests { .createAction(action) .flatMap(createdAction -> newActionService.findById(createdAction.getId(), READ_ACTIONS)); }) - .then(exportApplicationService - .exportApplicationById(savedApplication.getId(), SerialiseApplicationObjective.VERSION_CONTROL) + .then(exportService + .exportByArtifactId(savedApplication.getId(), VERSION_CONTROL, APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) .flatMap(applicationJson -> importService.importArtifactInWorkspaceFromGit( workspaceId, savedApplication.getId(), applicationJson, gitData.getBranchName()))) .map(importableArtifact -> (Application) importableArtifact) @@ -1437,7 +1436,7 @@ public class ImportServiceTests { public void importApplication_incompatibleJsonFile_throwException() { FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/incompatible_version.json"); Mono resultMono = importService - .extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, null, ArtifactJsonType.APPLICATION) + .extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, null, APPLICATION) .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); StepVerifier.create(resultMono) @@ -1459,7 +1458,7 @@ public class ImportServiceTests { final Mono resultMono = workspaceMono .flatMap(workspace -> importService.extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspace.getId(), null, ArtifactJsonType.APPLICATION)) + filePart, workspace.getId(), null, APPLICATION)) .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); List permissionGroups = workspaceMono @@ -1785,8 +1784,9 @@ public class ImportServiceTests { anonymousPermissionGroup.getId())) .build(); - Mono applicationMono = exportApplicationService - .exportApplicationById(application.getId(), "master") + Mono applicationMono = exportService + .exportByArtifactIdAndBranchName(application.getId(), "master", APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) .flatMap(applicationJson -> importService .importArtifactInWorkspaceFromGit(workspaceId, application.getId(), applicationJson, "master") .map(artifact -> (Application) artifact)); @@ -2832,7 +2832,8 @@ public class ImportServiceTests { .then(layoutActionService.createSingleAction(action2, Boolean.FALSE)) .then(updateLayoutService.updateLayout( testPage.getId(), testPage.getApplicationId(), layout.getId(), layout)) - .then(exportApplicationService.exportApplicationById(testApp.getId(), "")); + .then(exportService.exportByArtifactIdAndBranchName(testApp.getId(), "", APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); }) .cache(); @@ -3174,8 +3175,9 @@ public class ImportServiceTests { // Deploy the current application applicationPageService.publish(testApplication.getId(), true).block(); - Mono applicationJsonMono = exportApplicationService - .exportApplicationById(testApplication.getId(), "") + Mono applicationJsonMono = exportService + .exportByArtifactIdAndBranchName(testApplication.getId(), "", APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) .cache(); StepVerifier.create(applicationJsonMono) @@ -3282,8 +3284,9 @@ public class ImportServiceTests { }) .block(); - Mono result = exportApplicationService - .exportApplicationById(savedApplication.getId(), SerialiseApplicationObjective.VERSION_CONTROL) + Mono result = exportService + .exportByArtifactId(savedApplication.getId(), VERSION_CONTROL, APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) .flatMap(applicationJson -> { // setting published mode resource as null, similar to the app json exported to git repo applicationJson.getExportedApplication().setPublishedApplicationDetail(null); @@ -3339,8 +3342,9 @@ public class ImportServiceTests { }) .block(); - Mono result = exportApplicationService - .exportApplicationById(savedApplication.getId(), SerialiseApplicationObjective.VERSION_CONTROL) + Mono result = exportService + .exportByArtifactId(savedApplication.getId(), VERSION_CONTROL, APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) .flatMap(applicationJson -> { // setting published mode resource as null, similar to the app json exported to git repo applicationJson.getExportedApplication().setPublishedAppLayout(null); @@ -3399,8 +3403,9 @@ public class ImportServiceTests { .reorderPage(testApplication.getId(), testPage2.getId(), 1, null) .block(); - Mono applicationJsonMono = exportApplicationService - .exportApplicationById(testApplication.getId(), "") + Mono applicationJsonMono = exportService + .exportByArtifactIdAndBranchName(testApplication.getId(), "", APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) .cache(); StepVerifier.create(applicationJsonMono) @@ -3669,7 +3674,9 @@ public class ImportServiceTests { String branchName = null; return applicationService .save(application) - .then(exportApplicationService.exportApplicationById(application.getId(), branchName)); + .then(exportService.exportByArtifactIdAndBranchName( + application.getId(), branchName, APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); }); StepVerifier.create(exportedAppJson) @@ -4411,7 +4418,7 @@ public class ImportServiceTests { .block(); Mono resultMono = importService - .extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, null, ArtifactJsonType.APPLICATION) + .extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, null, APPLICATION) .map(artifactImportDTO -> (ApplicationImportDTO) artifactImportDTO); StepVerifier.create(resultMono) @@ -4508,8 +4515,9 @@ public class ImportServiceTests { return layoutActionService .createSingleAction(action, Boolean.FALSE) - .then(exportApplicationService.exportApplicationById( - objects.getT1().getId(), "")); + .then(exportService.exportByArtifactIdAndBranchName( + objects.getT1().getId(), "", APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); }); StepVerifier.create(exportAppMono) @@ -4536,8 +4544,9 @@ public class ImportServiceTests { .createApplication(application, workspaceId) .block(); - Mono resultMono = - exportApplicationService.exportApplicationById(createdApplication.getId(), ""); + Mono resultMono = exportService + .exportByArtifactIdAndBranchName(createdApplication.getId(), "", APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); StepVerifier.create(resultMono) .assertNext(applicationJson -> { @@ -4573,8 +4582,9 @@ public class ImportServiceTests { PageDTO applicationPageDTO = applicationPageService.createPage(pageDTO).block(); - Mono resultMono = - exportApplicationService.exportApplicationById(applicationPageDTO.getApplicationId(), ""); + Mono resultMono = exportService + .exportByArtifactIdAndBranchName(applicationPageDTO.getApplicationId(), "", APPLICATION) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); StepVerifier.create(resultMono) .assertNext(applicationJson -> { @@ -4691,7 +4701,7 @@ public class ImportServiceTests { FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); String workspaceId = createTemplateWorkspace().getId(); final Mono resultMonoWithoutDiscardOperation = importService - .extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, null, ArtifactJsonType.APPLICATION) + .extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, null, APPLICATION) .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO) .flatMap(applicationImportDTO -> { PageDTO page = new PageDTO(); @@ -4747,7 +4757,7 @@ public class ImportServiceTests { final Mono resultMonoWithDiscardOperation = resultMonoWithoutDiscardOperation .flatMap(importedApplication -> applicationService.save(importedApplication)) .flatMap(savedApplication -> importService.extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspaceId, savedApplication.getId(), ArtifactJsonType.APPLICATION)) + filePart, workspaceId, savedApplication.getId(), APPLICATION)) .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO) .map(ApplicationImportDTO::getApplication); @@ -4802,8 +4812,7 @@ public class ImportServiceTests { FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); final Mono resultMono = importService - .extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspaceId, application.getId(), ArtifactJsonType.APPLICATION) + .extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, application.getId(), APPLICATION) .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); StepVerifier.create(resultMono) @@ -4830,8 +4839,9 @@ public class ImportServiceTests { return isJSLibAdded; }) .cache(); - Mono getExportedAppMono = - addJSLibMonoCached.then(exportApplicationService.exportApplicationById(testAppId, "")); + Mono getExportedAppMono = addJSLibMonoCached + .then(exportService.exportByArtifactIdAndBranchName(testAppId, "", APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); StepVerifier.create(Mono.zip(addJSLibMonoCached, getExportedAppMono)) .assertNext(tuple2 -> { Boolean isJSLibAdded = tuple2.getT1(); @@ -4890,8 +4900,7 @@ public class ImportServiceTests { FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); final Mono resultMono = importService - .extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspaceId, application.getId(), ArtifactJsonType.APPLICATION) + .extractArtifactExchangeJsonAndSaveArtifact(filePart, workspaceId, application.getId(), APPLICATION) .map(importableArtifactDTO -> (ApplicationImportDTO) importableArtifactDTO); StepVerifier.create(resultMono) @@ -4926,7 +4935,7 @@ public class ImportServiceTests { .flatMap(application -> { FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); return importService - .extractArtifactExchangeJson(filePart, ArtifactJsonType.APPLICATION) + .extractArtifactExchangeJson(filePart, APPLICATION) .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson) .flatMap(applicationJson -> importService.mergeArtifactExchangeJsonWithImportableArtifact( workspaceId, application.getId(), null, applicationJson, null)) @@ -4962,7 +4971,7 @@ public class ImportServiceTests { FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json"); return importService .extractArtifactExchangeJsonAndSaveArtifact( - filePart, workspaceId, application.getId(), ArtifactJsonType.APPLICATION) + filePart, workspaceId, application.getId(), APPLICATION) .map(artifactImportDTO -> (ApplicationImportDTO) artifactImportDTO); }); @@ -5058,8 +5067,8 @@ public class ImportServiceTests { return newPageService .updatePage(applicationPage.getId(), pageDTO) // export the application - .then(exportApplicationService.exportApplicationById( - application.getId(), SerialiseApplicationObjective.VERSION_CONTROL)); + .then(exportService.exportByArtifactId(application.getId(), VERSION_CONTROL, APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); }); // verify that the exported json has the updated page name, and the queries are in the updated resources @@ -5158,8 +5167,8 @@ public class ImportServiceTests { datasource.setName("DS_FOR_RENAME_TEST_RENAMED"); return datasourceService .save(datasource) - .then(exportApplicationService.exportApplicationById( - application.getId(), SerialiseApplicationObjective.VERSION_CONTROL)); + .then(exportService.exportByArtifactId(application.getId(), VERSION_CONTROL, APPLICATION)) + .map(artifactExchangeJson -> (ApplicationJson) artifactExchangeJson); }); // verify that the exported json has the updated page name, and the queries are in the updated resources diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationSnapshotServiceUnitTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationSnapshotServiceUnitTest.java index 1d1f26c2dd..de0a2a5930 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationSnapshotServiceUnitTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationSnapshotServiceUnitTest.java @@ -2,7 +2,7 @@ package com.appsmith.server.services.ce; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.applications.base.ApplicationService; -import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.constants.SerialiseArtifactObjective; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationPage; import com.appsmith.server.domains.ApplicationSnapshot; @@ -88,7 +88,7 @@ public class ApplicationSnapshotServiceUnitTest { .thenReturn(Mono.just(branchedAppId)); Mockito.when(exportApplicationService.exportApplicationById( - branchedAppId, SerialiseApplicationObjective.VERSION_CONTROL)) + branchedAppId, SerialiseArtifactObjective.VERSION_CONTROL)) .thenReturn(Mono.just(applicationJson)); Mockito.when(applicationSnapshotRepository.deleteAllByApplicationId(branchedAppId)) diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportApplicationServiceTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportApplicationServiceTests.java index ff4791acb8..c0709ad443 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportApplicationServiceTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportApplicationServiceTests.java @@ -20,7 +20,7 @@ import com.appsmith.external.models.SSLDetails; import com.appsmith.server.actioncollections.base.ActionCollectionService; import com.appsmith.server.applications.base.ApplicationService; import com.appsmith.server.constants.FieldName; -import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.constants.SerialiseArtifactObjective; import com.appsmith.server.datasources.base.DatasourceService; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; @@ -787,7 +787,7 @@ public class ImportApplicationServiceTests { return layoutActionService .createAction(action) .then(exportApplicationService.exportApplicationById( - testApp.getId(), SerialiseApplicationObjective.VERSION_CONTROL)); + testApp.getId(), SerialiseArtifactObjective.VERSION_CONTROL)); }); StepVerifier.create(resultMono) @@ -1442,7 +1442,7 @@ public class ImportApplicationServiceTests { .flatMap(createdAction -> newActionService.findById(createdAction.getId(), READ_ACTIONS)); }) .then(exportApplicationService - .exportApplicationById(savedApplication.getId(), SerialiseApplicationObjective.VERSION_CONTROL) + .exportApplicationById(savedApplication.getId(), SerialiseArtifactObjective.VERSION_CONTROL) .flatMap(applicationJson -> importApplicationService.importApplicationInWorkspaceFromGit( workspaceId, applicationJson, savedApplication.getId(), gitData.getBranchName()))) .cache(); @@ -3035,7 +3035,7 @@ public class ImportApplicationServiceTests { @WithUserDetails(value = "usertest@usertest.com") public void exportApplication_withReadOnlyAccess_exportedWithDecryptedFields() { Mono exportApplicationMono = exportApplicationService.exportApplicationById( - exportWithConfigurationAppId, SerialiseApplicationObjective.SHARE); + exportWithConfigurationAppId, SerialiseArtifactObjective.SHARE); StepVerifier.create(exportApplicationMono) .assertNext(applicationJson -> { @@ -3319,7 +3319,7 @@ public class ImportApplicationServiceTests { .block(); Mono result = exportApplicationService - .exportApplicationById(savedApplication.getId(), SerialiseApplicationObjective.VERSION_CONTROL) + .exportApplicationById(savedApplication.getId(), SerialiseArtifactObjective.VERSION_CONTROL) .flatMap(applicationJson -> { // setting published mode resource as null, similar to the app json exported to git repo applicationJson.getExportedApplication().setPublishedApplicationDetail(null); @@ -3374,7 +3374,7 @@ public class ImportApplicationServiceTests { .block(); Mono result = exportApplicationService - .exportApplicationById(savedApplication.getId(), SerialiseApplicationObjective.VERSION_CONTROL) + .exportApplicationById(savedApplication.getId(), SerialiseArtifactObjective.VERSION_CONTROL) .flatMap(applicationJson -> { // setting published mode resource as null, similar to the app json exported to git repo applicationJson.getExportedApplication().setPublishedAppLayout(null); @@ -5069,7 +5069,7 @@ public class ImportApplicationServiceTests { .updatePage(applicationPage.getId(), pageDTO) // export the application .then(exportApplicationService.exportApplicationById( - application.getId(), SerialiseApplicationObjective.VERSION_CONTROL)); + application.getId(), SerialiseArtifactObjective.VERSION_CONTROL)); }); // verify that the exported json has the updated page name, and the queries are in the updated resources @@ -5169,7 +5169,7 @@ public class ImportApplicationServiceTests { return datasourceService .save(datasource) .then(exportApplicationService.exportApplicationById( - application.getId(), SerialiseApplicationObjective.VERSION_CONTROL)); + application.getId(), SerialiseArtifactObjective.VERSION_CONTROL)); }); // verify that the exported json has the updated page name, and the queries are in the updated resources