diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ApplicationJson.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ApplicationJson.java index 06479c6f25..344cc51e0d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ApplicationJson.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ApplicationJson.java @@ -18,19 +18,21 @@ import java.util.Set; public class ApplicationJson { Application exportedApplication; - + List datasourceList; - + List pageList; - + String publishedDefaultPageName; - + String unpublishedDefaultPageName; - + List actionList; - + + List actionCollectionList; + Map decryptedFields; - + /** * Mapping mongoEscapedWidgets with layoutId */ diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomActionCollectionRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomActionCollectionRepository.java index 3d504fb907..36a3461db8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomActionCollectionRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomActionCollectionRepository.java @@ -8,6 +8,9 @@ import reactor.core.publisher.Flux; import java.util.List; public interface CustomActionCollectionRepository extends AppsmithRepository { + + Flux findByApplicationId(String applicationId, AclPermission aclPermission, Sort sort); + Flux findByApplicationIdAndViewMode(String applicationId, boolean viewMode, AclPermission aclPermission); Flux findAllActionCollectionsByNameAndPageIdsAndViewMode(String name, List pageIds, boolean viewMode, AclPermission aclPermission, Sort sort); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomActionCollectionRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomActionCollectionRepositoryImpl.java index 48991f6f7b..80f19d3eaa 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomActionCollectionRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomActionCollectionRepositoryImpl.java @@ -19,6 +19,15 @@ public class CustomActionCollectionRepositoryImpl extends BaseAppsmithRepository super(mongoOperations, mongoConverter); } + + @Override + public Flux findByApplicationId(String applicationId, AclPermission aclPermission, Sort sort) { + + Criteria applicationCriteria = where(fieldName(QActionCollection.actionCollection.applicationId)).is(applicationId); + + return queryAll(List.of(applicationCriteria), aclPermission, sort); + } + @Override public Flux findByApplicationIdAndViewMode(String applicationId, boolean viewMode, AclPermission aclPermission) { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionService.java index 204e92b412..6a899cb49d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionService.java @@ -29,7 +29,6 @@ public interface ActionCollectionService extends CrudService getActionCollectionsByViewMode(MultiValueMap params, Boolean viewMode); - Mono update(String id, ActionCollectionDTO actionCollectionDTO); Mono deleteUnpublishedActionCollection(String id); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationService.java index a28473ea22..b1a2b5c816 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationService.java @@ -6,28 +6,31 @@ import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.BaseDomain; import com.appsmith.external.models.BasicAuth; import com.appsmith.external.models.DBAuth; +import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DecryptedSensitiveFields; import com.appsmith.external.models.OAuth2; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationJson; import com.appsmith.server.domains.ApplicationPage; -import com.appsmith.external.models.Datasource; import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; -import com.appsmith.server.domains.PluginType; import com.appsmith.server.domains.User; +import com.appsmith.server.dtos.ActionCollectionDTO; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.repositories.ActionCollectionRepository; import com.appsmith.server.repositories.DatasourceRepository; import com.appsmith.server.repositories.NewActionRepository; import com.appsmith.server.repositories.NewPageRepository; import com.appsmith.server.repositories.PluginRepository; +import com.appsmith.server.services.ActionCollectionService; import com.appsmith.server.services.ApplicationPageService; import com.appsmith.server.services.ApplicationService; import com.appsmith.server.services.DatasourceService; @@ -76,6 +79,8 @@ public class ImportExportApplicationService { private final NewActionService newActionService; private final SequenceService sequenceService; private final ExamplesOrganizationCloner examplesOrganizationCloner; + private final ActionCollectionRepository actionCollectionRepository; + private final ActionCollectionService actionCollectionService; private static final Set ALLOWED_CONTENT_TYPES = Set.of(MediaType.APPLICATION_JSON); private static final String INVALID_JSON_FILE = "invalid json file"; @@ -86,6 +91,7 @@ public class ImportExportApplicationService { /** * This function will give the application resource to rebuild the application in import application flow + * * @param applicationId which needs to be exported * @return application reference from which entire application can be rehydrated */ @@ -97,187 +103,212 @@ public class ImportExportApplicationService { 3. Fetch datasources from organization 4. Fetch actions from the application 5. Filter out relevant datasources using actions reference + 6. Fetch action collections from the application */ ApplicationJson applicationJson = new ApplicationJson(); Map pluginMap = new HashMap<>(); Map datasourceIdToNameMap = new HashMap<>(); Map pageIdToNameMap = new HashMap<>(); - + if (applicationId == null || applicationId.isEmpty()) { return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.APPLICATION_ID)); } Mono applicationMono = applicationService.findById(applicationId, AclPermission.EXPORT_APPLICATIONS) - .switchIfEmpty(Mono.error( - new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION_ID, applicationId)) - ); + .switchIfEmpty(Mono.error( + new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION_ID, applicationId)) + ); return pluginRepository - .findAll() - .map(plugin -> { - pluginMap.put(plugin.getId(), plugin.getPackageName()); - return plugin; - }) - .then(applicationMono) - .flatMap(application -> { + .findAll() + .map(plugin -> { + pluginMap.put(plugin.getId(), plugin.getPackageName()); + return plugin; + }) + .then(applicationMono) + .flatMap(application -> { - // Assign the default page names for published and unpublished field in applicationJson object - ApplicationPage unpublishedDefaultPage = application.getPages() - .stream() - .filter(ApplicationPage::getIsDefault) - .findFirst() - .orElse(null); - - if (unpublishedDefaultPage == null) { - return Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.DEFAULT_PAGE_NAME)); - } else { - applicationJson.setUnpublishedDefaultPageName(unpublishedDefaultPage.getId()); - } - - if (application.getPublishedPages() != null) { - application.getPublishedPages() - .stream() - .filter(ApplicationPage::getIsDefault) - .findFirst() - .ifPresent( - publishedDefaultPage -> applicationJson.setPublishedDefaultPageName(publishedDefaultPage.getId()) - ); - } + // Assign the default page names for published and unpublished field in applicationJson object + ApplicationPage unpublishedDefaultPage = application.getPages() + .stream() + .filter(ApplicationPage::getIsDefault) + .findFirst() + .orElse(null); - // Refactor application to remove the ids - final String organizationId = application.getOrganizationId(); - application.setOrganizationId(null); - application.setPages(null); - application.setPublishedPages(null); - application.setModifiedBy(null); - application.setUpdatedAt(null); - application.setLastDeployedAt(null); - application.setGitApplicationMetadata(null); - examplesOrganizationCloner.makePristine(application); - applicationJson.setExportedApplication(application); - return newPageRepository.findByApplicationId(applicationId, AclPermission.MANAGE_PAGES) - .collectList() - .flatMap(newPageList -> { - // Extract mongoEscapedWidgets from pages and save it to applicationJson object as this - // field is JsonIgnored. Also remove any ids those are present in the page objects + if (unpublishedDefaultPage == null) { + return Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.DEFAULT_PAGE_NAME)); + } else { + applicationJson.setUnpublishedDefaultPageName(unpublishedDefaultPage.getId()); + } - Map> publishedMongoEscapedWidgetsNames = new HashMap<>(); - Map> unpublishedMongoEscapedWidgetsNames = new HashMap<>(); - newPageList.forEach(newPage -> { - - if (newPage.getUnpublishedPage() != null) { - pageIdToNameMap.put( - newPage.getId() + PublishType.UNPUBLISHED, newPage.getUnpublishedPage().getName() + if (application.getPublishedPages() != null) { + application.getPublishedPages() + .stream() + .filter(ApplicationPage::getIsDefault) + .findFirst() + .ifPresent( + publishedDefaultPage -> applicationJson.setPublishedDefaultPageName(publishedDefaultPage.getId()) ); - PageDTO unpublishedPageDTO = newPage.getUnpublishedPage(); - if (StringUtils.equals( - applicationJson.getUnpublishedDefaultPageName(), newPage.getId()) - ) { - applicationJson.setUnpublishedDefaultPageName(unpublishedPageDTO.getName()); - } - if (unpublishedPageDTO.getLayouts() != null) { - - unpublishedPageDTO.getLayouts().forEach(layout -> - unpublishedMongoEscapedWidgetsNames - .put(layout.getId(), layout.getMongoEscapedWidgetNames()) + } + + // Refactor application to remove the ids + final String organizationId = application.getOrganizationId(); + application.setOrganizationId(null); + application.setPages(null); + application.setPublishedPages(null); + application.setModifiedBy(null); + application.setUpdatedAt(null); + application.setLastDeployedAt(null); + application.setGitApplicationMetadata(null); + examplesOrganizationCloner.makePristine(application); + applicationJson.setExportedApplication(application); + // TODO @Abhijeet please take a look at the ACL permissions + return newPageRepository.findByApplicationId(applicationId, AclPermission.MANAGE_PAGES) + .collectList() + .flatMap(newPageList -> { + // Extract mongoEscapedWidgets from pages and save it to applicationJson object as this + // field is JsonIgnored. Also remove any ids those are present in the page objects + + Map> publishedMongoEscapedWidgetsNames = new HashMap<>(); + Map> unpublishedMongoEscapedWidgetsNames = new HashMap<>(); + newPageList.forEach(newPage -> { + + if (newPage.getUnpublishedPage() != null) { + pageIdToNameMap.put( + newPage.getId() + PublishType.UNPUBLISHED, newPage.getUnpublishedPage().getName() + ); + PageDTO unpublishedPageDTO = newPage.getUnpublishedPage(); + if (StringUtils.equals( + applicationJson.getUnpublishedDefaultPageName(), newPage.getId()) + ) { + applicationJson.setUnpublishedDefaultPageName(unpublishedPageDTO.getName()); + } + if (unpublishedPageDTO.getLayouts() != null) { + + unpublishedPageDTO.getLayouts().forEach(layout -> + unpublishedMongoEscapedWidgetsNames + .put(layout.getId(), layout.getMongoEscapedWidgetNames()) + ); + } + } + + if (newPage.getPublishedPage() != null) { + pageIdToNameMap.put( + newPage.getId() + PublishType.PUBLISHED, newPage.getPublishedPage().getName() + ); + PageDTO publishedPageDTO = newPage.getPublishedPage(); + if (applicationJson.getPublishedDefaultPageName() != null && + StringUtils.equals( + applicationJson.getPublishedDefaultPageName(), newPage.getId() + ) + ) { + applicationJson.setPublishedDefaultPageName(publishedPageDTO.getName()); + } + + if (publishedPageDTO.getLayouts() != null) { + newPage.getPublishedPage().getLayouts().forEach(layout -> + publishedMongoEscapedWidgetsNames + .put(layout.getId(), layout.getMongoEscapedWidgetNames()) + ); + } + } + newPage.setApplicationId(null); + examplesOrganizationCloner.makePristine(newPage); + }); + applicationJson.setPageList(newPageList); + applicationJson.setPublishedLayoutmongoEscapedWidgets(publishedMongoEscapedWidgetsNames); + applicationJson.setUnpublishedLayoutmongoEscapedWidgets(unpublishedMongoEscapedWidgetsNames); + return datasourceRepository + .findAllByOrganizationId(organizationId, AclPermission.MANAGE_DATASOURCES) + .collectList(); + }) + .flatMapMany(datasourceList -> { + datasourceList.forEach(datasource -> + datasourceIdToNameMap.put(datasource.getId(), datasource.getName())); + + applicationJson.setDatasourceList(datasourceList); + return newActionRepository + .findByApplicationId(applicationId, AclPermission.MANAGE_ACTIONS, null); + }) + .collectList() + .flatMapMany(newActionList -> { + Set concernedDBNames = new HashSet<>(); + newActionList.forEach(newAction -> { + newAction.setPluginId(pluginMap.get(newAction.getPluginId())); + newAction.setOrganizationId(null); + newAction.setPolicies(null); + newAction.setApplicationId(null); + concernedDBNames.add( + sanitizeDatasourceInActionDTO(newAction.getPublishedAction(), datasourceIdToNameMap, pluginMap, null) ); - } - } - - if (newPage.getPublishedPage() != null) { - pageIdToNameMap.put( - newPage.getId() + PublishType.PUBLISHED, newPage.getPublishedPage().getName() - ); - PageDTO publishedPageDTO = newPage.getPublishedPage(); - if (applicationJson.getPublishedDefaultPageName() != null && - StringUtils.equals( - applicationJson.getPublishedDefaultPageName(), newPage.getId() - ) - ) { - applicationJson.setPublishedDefaultPageName(publishedPageDTO.getName()); - } - - if (publishedPageDTO.getLayouts() != null) { - newPage.getPublishedPage().getLayouts().forEach(layout -> - publishedMongoEscapedWidgetsNames - .put(layout.getId(), layout.getMongoEscapedWidgetNames()) + concernedDBNames.add( + sanitizeDatasourceInActionDTO(newAction.getUnpublishedAction(), datasourceIdToNameMap, pluginMap, null) ); + + if (newAction.getUnpublishedAction() != null) { + ActionDTO actionDTO = newAction.getUnpublishedAction(); + actionDTO.setPageId(pageIdToNameMap.get(actionDTO.getPageId() + PublishType.UNPUBLISHED)); + } + if (newAction.getPublishedAction() != null) { + ActionDTO actionDTO = newAction.getPublishedAction(); + actionDTO.setPageId(pageIdToNameMap.get(actionDTO.getPageId() + PublishType.PUBLISHED)); + } + }); + // This is where we're removing global datasources that are unused in this application + applicationJson + .getDatasourceList() + .removeIf(datasource -> !concernedDBNames.contains(datasource.getName())); + + applicationJson.setActionList(newActionList); + + Map decryptedFields = new HashMap<>(); + applicationJson.getDatasourceList().forEach(datasource -> { + decryptedFields.put(datasource.getName(), getDecryptedFields(datasource)); + datasource.setId(null); + datasource.setOrganizationId(null); + datasource.setPluginId(pluginMap.get(datasource.getPluginId())); + datasource.setStructure(null); + if (SerialiseApplicationObjective.VERSION_CONTROL.equals(serialiseFor)) { + // If we are exporting the doc for git functionality, remove the datasourceConfiguration + // object as user will configure it once imported to other instance + datasource.setDatasourceConfiguration(null); + } else if (datasource.getDatasourceConfiguration() != null) { + datasource.getDatasourceConfiguration().setAuthentication(null); + } + }); + applicationJson.setDecryptedFields(decryptedFields); + return actionCollectionRepository + .findByApplicationId(applicationId, AclPermission.MANAGE_ACTIONS, null); + }) + .map(actionCollection -> { + // Remove references to ids since the serialized version does not have this information + actionCollection.setOrganizationId(null); + actionCollection.setPolicies(null); + actionCollection.setApplicationId(null); + + if (actionCollection.getUnpublishedCollection() != null) { + ActionCollectionDTO actionCollectionDTO = actionCollection.getUnpublishedCollection(); + actionCollectionDTO.setPageId(pageIdToNameMap.get(actionCollectionDTO.getPageId() + PublishType.UNPUBLISHED)); + actionCollectionDTO.setPluginId(pluginMap.get(actionCollectionDTO.getPluginId())); } - } - newPage.setApplicationId(null); - examplesOrganizationCloner.makePristine(newPage); - }); - applicationJson.setPageList(newPageList); - applicationJson.setPublishedLayoutmongoEscapedWidgets(publishedMongoEscapedWidgetsNames); - applicationJson.setUnpublishedLayoutmongoEscapedWidgets(unpublishedMongoEscapedWidgetsNames); - return datasourceRepository - .findAllByOrganizationId(organizationId, AclPermission.MANAGE_DATASOURCES) - .collectList(); - }) - .flatMapMany(datasourceList -> { - datasourceList.forEach(datasource -> - datasourceIdToNameMap.put(datasource.getId(), datasource.getName())); - - applicationJson.setDatasourceList(datasourceList); - return newActionRepository - .findByApplicationId(applicationId, AclPermission.MANAGE_ACTIONS, null); - }) - .collectList() - .map(newActionList -> { - Set concernedDBNames = new HashSet<>(); - newActionList.forEach(newAction -> { - newAction.setPluginId(pluginMap.get(newAction.getPluginId())); - newAction.setOrganizationId(null); - newAction.setPolicies(null); - newAction.setApplicationId(null); - //Collect Datasource names to filter only required datasources - if (PluginType.DB.equals(newAction.getPluginType()) - || PluginType.API.equals(newAction.getPluginType()) - || PluginType.SAAS.equals(newAction.getPluginType())) { - concernedDBNames.add( - sanitizeDatasourceInActionDTO(newAction.getPublishedAction(), datasourceIdToNameMap, pluginMap, null) - ); - concernedDBNames.add( - sanitizeDatasourceInActionDTO(newAction.getUnpublishedAction(), datasourceIdToNameMap, pluginMap, null) - ); - } - if (newAction.getUnpublishedAction() != null) { - ActionDTO actionDTO = newAction.getUnpublishedAction(); - actionDTO.setPageId(pageIdToNameMap.get(actionDTO.getPageId() + PublishType.UNPUBLISHED)); - } - if (newAction.getPublishedAction() != null) { - ActionDTO actionDTO = newAction.getPublishedAction(); - actionDTO.setPageId(pageIdToNameMap.get(actionDTO.getPageId() + PublishType.PUBLISHED)); - } - }); - applicationJson - .getDatasourceList() - .removeIf(datasource -> !concernedDBNames.contains(datasource.getName())); - - applicationJson.setActionList(newActionList); - - //Only export those datasources which are used in the app instead of org level - Map decryptedFields = new HashMap<>(); - applicationJson.getDatasourceList().forEach(datasource -> { - decryptedFields.put(datasource.getName(), getDecryptedFields(datasource)); - datasource.setId(null); - datasource.setOrganizationId(null); - datasource.setPluginId(pluginMap.get(datasource.getPluginId())); - datasource.setStructure(null); - if (SerialiseApplicationObjective.VERSION_CONTROL.equals(serialiseFor)) { - // If we are exporting the doc for git functionality, remove the datasourceConfiguration - // object as user will configure it once imported to other instance - datasource.setDatasourceConfiguration(null); - } else if (datasource.getDatasourceConfiguration() != null) { - datasource.getDatasourceConfiguration().setAuthentication(null); - } - }); - applicationJson.setDecryptedFields(decryptedFields); - return applicationJson; - }); - }) - .then() - .thenReturn(applicationJson); + if (actionCollection.getPublishedCollection() != null) { + ActionCollectionDTO actionCollectionDTO = actionCollection.getPublishedCollection(); + actionCollectionDTO.setPageId(pageIdToNameMap.get(actionCollectionDTO.getPageId() + PublishType.PUBLISHED)); + actionCollectionDTO.setPluginId(pluginMap.get(actionCollectionDTO.getPluginId())); + } + + return actionCollection; + }) + .collectList() + .map(actionCollections -> { + // This object won't have the list of actions but we don't care about that today + // Because the actions will have a reference to the collection + applicationJson.setActionCollectionList(actionCollections); + return applicationJson; + }); + }) + .then() + .thenReturn(applicationJson); } public Mono exportApplicationById(String applicationId) { @@ -286,7 +317,8 @@ public class ImportExportApplicationService { /** * This function will take the Json filepart and saves the application in organization - * @param orgId organization to which the application needs to be hydrated + * + * @param orgId organization to which the application needs to be hydrated * @param filePart Json file which contains the entire application object * @return saved application in DB */ @@ -309,17 +341,18 @@ public class ImportExportApplicationService { final Flux contentCache = filePart.content().cache(); Mono stringifiedFile = DataBufferUtils.join(contentCache) - .map(dataBuffer -> { - byte[] data = new byte[dataBuffer.readableByteCount()]; - dataBuffer.read(data); - DataBufferUtils.release(dataBuffer); - return new String(data); - }); + .map(dataBuffer -> { + byte[] data = new byte[dataBuffer.readableByteCount()]; + dataBuffer.read(data); + DataBufferUtils.release(dataBuffer); + return new String(data); + }); return stringifiedFile .flatMap(data -> { Gson gson = new Gson(); - Type fileType = new TypeToken() {}.getType(); + Type fileType = new TypeToken() { + }.getType(); ApplicationJson jsonFile = gson.fromJson(data, fileType); return importApplicationInOrganization(orgId, jsonFile); }); @@ -327,8 +360,9 @@ public class ImportExportApplicationService { /** * This function will save the application to organisation from the application resource + * * @param organizationId organization to which application is going to be stored - * @param importedDoc application resource which contains necessary information to save the application + * @param importedDoc application resource which contains necessary information to save the application * @return saved application in DB */ public Mono importApplicationInOrganization(String organizationId, ApplicationJson importedDoc) { @@ -337,9 +371,10 @@ public class ImportExportApplicationService { /** * This function will take the application reference object to hydrate the application in mongoDB + * * @param organizationId organization to which application is going to be stored - * @param importedDoc application resource which contains necessary information to save the application - * @param applicationId application which needs to be saved with the updated resources + * @param importedDoc application resource which contains necessary information to save the application + * @param applicationId application which needs to be saved with the updated resources * @return Updated application */ public Mono importApplicationInOrganization(String organizationId, @@ -358,16 +393,21 @@ public class ImportExportApplicationService { Map datasourceMap = new HashMap<>(); Map pageNameMap = new HashMap<>(); Map actionIdMap = new HashMap<>(); + Map> unpublishedActionCollectionIdMap = new HashMap<>(); + Map> publishedActionCollectionIdMap = new HashMap<>(); + Map unpublishedActionIdToCollectionIdMap = new HashMap<>(); + Map publishedActionIdToCollectionIdMap = new HashMap<>(); Application importedApplication = importedDoc.getExportedApplication(); List importedDatasourceList = importedDoc.getDatasourceList(); List importedNewPageList = importedDoc.getPageList(); List importedNewActionList = importedDoc.getActionList(); + List importedActionCollectionList = importedDoc.getActionCollectionList(); Mono currUserMono = sessionUserService.getCurrentUser().cache(); final Flux existingDatasourceFlux = datasourceRepository - .findAllByOrganizationId(organizationId, AclPermission.MANAGE_DATASOURCES) - .cache(); + .findAllByOrganizationId(organizationId, AclPermission.MANAGE_DATASOURCES) + .cache(); String errorField = ""; if (importedNewPageList == null || importedNewPageList.isEmpty()) { @@ -378,275 +418,358 @@ public class ImportExportApplicationService { errorField = FieldName.ACTIONS; } else if (importedDatasourceList == null) { errorField = FieldName.DATASOURCE; + } else if (importedActionCollectionList == null) { + errorField = FieldName.ACTION_COLLECTION; } - if(!errorField.isEmpty()) { + if (!errorField.isEmpty()) { return Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, errorField, INVALID_JSON_FILE)); } return pluginRepository.findAll() - .map(plugin -> { - pluginMap.put(plugin.getPackageName(), plugin.getId()); - return plugin; - }) - .then(organizationService.findById(organizationId, AclPermission.ORGANIZATION_MANAGE_APPLICATIONS)) - .switchIfEmpty(Mono.error( - new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.ORGANIZATION, organizationId)) - ) - .flatMap(organization -> { - // Check if the request is to hydrate the application to DB for particular branch - if (applicationId != null) { - // No need to hydrate the datasource as we expect user will configure the datasource - return existingDatasourceFlux.collectList(); - } - return Mono.just(new ArrayList()); - }) - .flatMap(existingDatasources -> { - Map savedDatasourcesGitIdToDatasourceMap = new HashMap<>(); - - existingDatasources.stream() - .filter(datasource -> datasource.getGitSyncId() != null) - .forEach(datasource -> savedDatasourcesGitIdToDatasourceMap.put(datasource.getGitSyncId(), datasource)); - - return Flux.fromIterable(importedDatasourceList) - //Check for duplicate datasources to avoid duplicates in target organization - .flatMap(datasource -> { - - // Check if the datasource has gitSyncId and if it's already in DB - if (datasource.getGitSyncId() != null - && savedDatasourcesGitIdToDatasourceMap.containsKey(datasource.getGitSyncId())) { - - //Since the resource is already present in DB, just update resource - Datasource existingDatasource = savedDatasourcesGitIdToDatasourceMap.get(datasource.getGitSyncId()); - datasource.setId(savedDatasourcesGitIdToDatasourceMap.get(datasource.getGitSyncId()).getId()); - // Don't update datasource config as the saved datasource is already configured as per user - // for this instance - datasource.setDatasourceConfiguration(null); - BeanCopyUtils.copyNewFieldValuesIntoOldObject(datasource, existingDatasource); - return datasourceService.update(datasource.getId(), existingDatasource); - } - - datasource.setPluginId(pluginMap.get(datasource.getPluginId())); - datasource.setOrganizationId(organizationId); - - //Check if any decrypted fields are present for datasource - if (importedDoc.getDecryptedFields().get(datasource.getName()) != null) { - - DecryptedSensitiveFields decryptedFields = - importedDoc.getDecryptedFields().get(datasource.getName()); - - updateAuthenticationDTO(datasource, decryptedFields); - } - return createUniqueDatasourceIfNotPresent(existingDatasourceFlux, datasource, organizationId); - }) - .map(datasource -> { - datasourceMap.put(datasource.getName(), datasource.getId()); - return datasource; - }) - .collectList(); - }) - .then( - // 1. Assign the policies for the imported application - // 2. Check for possible duplicate names, - // 3. Save the updated application - applicationPageService.setApplicationPolicies(currUserMono, organizationId, importedApplication) - .zipWith(currUserMono) - .map(objects -> { - Application application = objects.getT1(); - application.setModifiedBy(objects.getT2().getUsername()); - return application; - }) - .flatMap(application -> { - if (applicationId != null) { - return applicationService.findById(applicationId, AclPermission.MANAGE_APPLICATIONS) - .switchIfEmpty( - Mono.error(new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION_ID, applicationId)) - ) - .flatMap(existingApplication -> { - importedApplication.setId(existingApplication.getId()); - BeanCopyUtils.copyNewFieldValuesIntoOldObject(importedApplication, existingApplication); - // Here we are expecting the changes present in DB are committed to git directory - // so that these won't be lost when we are pulling changes from remote and - // rehydrate the application. We are now rehydrating the application with/without - // the changes from remote - - return applicationService.update(applicationId, existingApplication); - }); - } - return applicationService - .findByOrganizationId(organizationId, AclPermission.MANAGE_APPLICATIONS) - .collectList() - .flatMap(applicationList -> { - - Application duplicateNameApp = applicationList - .stream() - .filter(application1 -> StringUtils.equals(application1.getName(), application.getName())) - .findAny() - .orElse(null); - - return getUniqueSuffixForDuplicateNameEntity(duplicateNameApp, organizationId) - .map(suffix -> { - importedApplication.setName(importedApplication.getName() + suffix); - return importedApplication; - }); - }) - .then(applicationService.save(importedApplication)); - }) - ) - .flatMap(savedApp -> { - importedApplication.setId(savedApp.getId()); - Map> applicationPages = Map.of( - PublishType.UNPUBLISHED, new ArrayList<>(), - PublishType.PUBLISHED, new ArrayList<>() - ); - - // Import and save pages, also update the pages related fields in saved application - assert importedNewPageList != null; - return importAndSavePages( - importedNewPageList, - importedApplication, - importedDoc.getPublishedLayoutmongoEscapedWidgets(), - importedDoc.getUnpublishedLayoutmongoEscapedWidgets() - ) - .map(newPage -> { - ApplicationPage unpublishedAppPage = new ApplicationPage(); - ApplicationPage publishedAppPage = new ApplicationPage(); - - if (newPage.getUnpublishedPage() != null && newPage.getUnpublishedPage().getName() != null) { - unpublishedAppPage.setIsDefault( - StringUtils.equals( - newPage.getUnpublishedPage().getName(), importedDoc.getUnpublishedDefaultPageName() - ) - ); - unpublishedAppPage.setId(newPage.getId()); - pageNameMap.put(newPage.getUnpublishedPage().getName(), newPage); - } - - if (newPage.getPublishedPage() != null && newPage.getPublishedPage().getName() != null) { - publishedAppPage.setIsDefault( - StringUtils.equals( - newPage.getPublishedPage().getName(), importedDoc.getPublishedDefaultPageName() - ) - ); - publishedAppPage.setId(newPage.getId()); - pageNameMap.put(newPage.getPublishedPage().getName(), newPage); - } - if (unpublishedAppPage.getId() != null) { - applicationPages.get(PublishType.UNPUBLISHED).add(unpublishedAppPage); - } - if (publishedAppPage.getId() != null) { - applicationPages.get(PublishType.PUBLISHED).add(publishedAppPage); - } - return applicationPages; + .map(plugin -> { + pluginMap.put(plugin.getPackageName(), plugin.getId()); + return plugin; }) - .then() - .thenReturn(applicationPages); - }) - .flatMap(applicationPageMap -> { - importedApplication.setPages(applicationPageMap.get(PublishType.UNPUBLISHED)); - importedApplication.setPublishedPages(applicationPageMap.get(PublishType.PUBLISHED)); + .then(organizationService.findById(organizationId, AclPermission.ORGANIZATION_MANAGE_APPLICATIONS)) + .switchIfEmpty(Mono.error( + new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.ORGANIZATION, organizationId)) + ) + .flatMap(organization -> { + // Check if the request is to hydrate the application to DB for particular branch + // Application id will be present for GIT sync + if (applicationId != null) { + // No need to hydrate the datasource as we expect user will configure the datasource + return existingDatasourceFlux.collectList(); + } + return Mono.just(new ArrayList()); + }) + .flatMap(existingDatasources -> { + Map savedDatasourcesGitIdToDatasourceMap = new HashMap<>(); - return newActionRepository.findByApplicationId(importedApplication.getId()) - .collectList(); - }) - .flatMap(existingActions -> { + existingDatasources.stream() + .filter(datasource -> datasource.getGitSyncId() != null) + .forEach(datasource -> savedDatasourcesGitIdToDatasourceMap.put(datasource.getGitSyncId(), datasource)); - Map savedActionsGitIdToActionsMap = new HashMap<>(); + return Flux.fromIterable(importedDatasourceList) + // Check for duplicate datasources to avoid duplicates in target organization + .flatMap(datasource -> { - existingActions.stream() - .filter(newAction -> newAction.getGitSyncId() != null) - .forEach(newAction -> savedActionsGitIdToActionsMap.put(newAction.getGitSyncId(), newAction)); + // Check if the datasource has gitSyncId and if it's already in DB + if (datasource.getGitSyncId() != null + && savedDatasourcesGitIdToDatasourceMap.containsKey(datasource.getGitSyncId())) { - assert importedNewActionList != null; + // Since the resource is already present in DB, just update resource + Datasource existingDatasource = savedDatasourcesGitIdToDatasourceMap.get(datasource.getGitSyncId()); + datasource.setId(savedDatasourcesGitIdToDatasourceMap.get(datasource.getGitSyncId()).getId()); + // Don't update datasource config as the saved datasource is already configured as per user + // for this instance + datasource.setDatasourceConfiguration(null); + BeanCopyUtils.copyNewFieldValuesIntoOldObject(datasource, existingDatasource); + return datasourceService.update(datasource.getId(), existingDatasource); + } - return Flux.fromIterable(importedNewActionList) - .flatMap(newAction -> { - NewPage parentPage = new NewPage(); - if (newAction.getUnpublishedAction() != null && newAction.getUnpublishedAction().getName() != null) { - parentPage = pageNameMap.get(newAction.getUnpublishedAction().getPageId()); - actionIdMap.put(newAction.getUnpublishedAction().getName() + parentPage.getId(), newAction.getId()); - newAction.getUnpublishedAction().setPageId(parentPage.getId()); - sanitizeDatasourceInActionDTO(newAction.getUnpublishedAction(), datasourceMap, pluginMap, organizationId); - } + // This is explicitly copied over from the map we created before + datasource.setPluginId(pluginMap.get(datasource.getPluginId())); + datasource.setOrganizationId(organizationId); - if (newAction.getPublishedAction() != null && newAction.getPublishedAction().getName() != null) { - parentPage = pageNameMap.get(newAction.getPublishedAction().getPageId()); - actionIdMap.put(newAction.getPublishedAction().getName() + parentPage.getId(), newAction.getId()); - newAction.getPublishedAction().setPageId(parentPage.getId()); - sanitizeDatasourceInActionDTO(newAction.getPublishedAction(), datasourceMap, pluginMap, organizationId); - } + // Check if any decrypted fields are present for datasource + if (importedDoc.getDecryptedFields().get(datasource.getName()) != null) { - examplesOrganizationCloner.makePristine(newAction); - newAction.setOrganizationId(organizationId); - newAction.setApplicationId(importedApplication.getId()); - newAction.setPluginId(pluginMap.get(newAction.getPluginId())); - newActionService.generateAndSetActionPolicies(parentPage, newAction); + DecryptedSensitiveFields decryptedFields = + importedDoc.getDecryptedFields().get(datasource.getName()); - // Check if the action has gitSyncId and if it's already in DB - if (newAction.getGitSyncId() != null - && savedActionsGitIdToActionsMap.containsKey(newAction.getGitSyncId())) { + updateAuthenticationDTO(datasource, decryptedFields); + } + return createUniqueDatasourceIfNotPresent(existingDatasourceFlux, datasource, organizationId); + }) + .map(datasource -> { + datasourceMap.put(datasource.getName(), datasource.getId()); + return datasource; + }) + .collectList(); + }) + .then( + // 1. Assign the policies for the imported application + // 2. Check for possible duplicate names, + // 3. Save the updated application + applicationPageService.setApplicationPolicies(currUserMono, organizationId, importedApplication) + .zipWith(currUserMono) + .map(objects -> { + Application application = objects.getT1(); + application.setModifiedBy(objects.getT2().getUsername()); + return application; + }) + .flatMap(application -> { + // Application Id will be present for GIT sync + if (applicationId != null) { + return applicationService.findById(applicationId, AclPermission.MANAGE_APPLICATIONS) + .switchIfEmpty( + Mono.error(new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION_ID, applicationId)) + ) + .flatMap(existingApplication -> { + importedApplication.setId(existingApplication.getId()); + BeanCopyUtils.copyNewFieldValuesIntoOldObject(importedApplication, existingApplication); + // Here we are expecting the changes present in DB are committed to git directory + // so that these won't be lost when we are pulling changes from remote and + // rehydrate the application. We are now rehydrating the application with/without + // the changes from remote - //Since the resource is already present in DB, just update resource - NewAction existingAction = savedActionsGitIdToActionsMap.get(newAction.getGitSyncId()); - newAction.setId(savedActionsGitIdToActionsMap.get(newAction.getGitSyncId()).getId()); - BeanCopyUtils.copyNewFieldValuesIntoOldObject(newAction, existingAction); - return newActionService.update(newAction.getId(), existingAction); - } - return newActionService.save(newAction); - }) - .map(newAction -> { - if (newAction.getUnpublishedAction() != null) { - ActionDTO unpublishedAction = newAction.getUnpublishedAction(); - actionIdMap.put( - actionIdMap.get(unpublishedAction.getName() + unpublishedAction.getPageId()), - newAction.getId() - ); - } - if (newAction.getPublishedAction() != null) { - ActionDTO publishedAction = newAction.getPublishedAction(); - actionIdMap.put( - actionIdMap.get(publishedAction.getName() + publishedAction.getPageId()), - newAction.getId() - ); - } - return newAction; - }) - .then() - .thenReturn(importedApplication); - }) - .flatMap(ignored -> { - //Map layoutOnLoadActions ids with relevant actions - assert importedNewPageList != null; - importedNewPageList.forEach(page -> mapActionIdWithPageLayout(page, actionIdMap)); - return Flux.fromIterable(importedNewPageList) - .flatMap(newPageService::save) - .then(applicationService.update(importedApplication.getId(), importedApplication)); - }); + return applicationService.update(applicationId, existingApplication); + }); + } + return applicationService + .findByOrganizationId(organizationId, AclPermission.MANAGE_APPLICATIONS) + .collectList() + .flatMap(applicationList -> { + + Application duplicateNameApp = applicationList + .stream() + .filter(application1 -> StringUtils.equals(application1.getName(), application.getName())) + .findAny() + .orElse(null); + + return getUniqueSuffixForDuplicateNameEntity(duplicateNameApp, organizationId) + .map(suffix -> { + importedApplication.setName(importedApplication.getName() + suffix); + return importedApplication; + }); + }) + .then(applicationService.save(importedApplication)); + }) + ) + .flatMap(savedApp -> { + importedApplication.setId(savedApp.getId()); + Map> applicationPages = Map.of( + PublishType.UNPUBLISHED, new ArrayList<>(), + PublishType.PUBLISHED, new ArrayList<>() + ); + + // Import and save pages, also update the pages related fields in saved application + assert importedNewPageList != null; + return importAndSavePages( + importedNewPageList, + importedApplication, + importedDoc.getPublishedLayoutmongoEscapedWidgets(), + importedDoc.getUnpublishedLayoutmongoEscapedWidgets() + ) + .map(newPage -> { + ApplicationPage unpublishedAppPage = new ApplicationPage(); + ApplicationPage publishedAppPage = new ApplicationPage(); + + if (newPage.getUnpublishedPage() != null && newPage.getUnpublishedPage().getName() != null) { + unpublishedAppPage.setIsDefault( + StringUtils.equals( + newPage.getUnpublishedPage().getName(), importedDoc.getUnpublishedDefaultPageName() + ) + ); + unpublishedAppPage.setId(newPage.getId()); + pageNameMap.put(newPage.getUnpublishedPage().getName(), newPage); + } + + if (newPage.getPublishedPage() != null && newPage.getPublishedPage().getName() != null) { + publishedAppPage.setIsDefault( + StringUtils.equals( + newPage.getPublishedPage().getName(), importedDoc.getPublishedDefaultPageName() + ) + ); + publishedAppPage.setId(newPage.getId()); + pageNameMap.put(newPage.getPublishedPage().getName(), newPage); + } + if (unpublishedAppPage.getId() != null) { + applicationPages.get(PublishType.UNPUBLISHED).add(unpublishedAppPage); + } + if (publishedAppPage.getId() != null) { + applicationPages.get(PublishType.PUBLISHED).add(publishedAppPage); + } + return applicationPages; + }) + .then() + .thenReturn(applicationPages); + }) + .flatMap(applicationPageMap -> { + importedApplication.setPages(applicationPageMap.get(PublishType.UNPUBLISHED)); + importedApplication.setPublishedPages(applicationPageMap.get(PublishType.PUBLISHED)); + + // This will be non-empty for GIT sync + return newActionRepository.findByApplicationId(importedApplication.getId()) + .collectList(); + }) + .flatMap(existingActions -> { + + Map savedActionsGitIdToActionsMap = new HashMap<>(); + + existingActions.stream() + .filter(newAction -> newAction.getGitSyncId() != null) + .forEach(newAction -> savedActionsGitIdToActionsMap.put(newAction.getGitSyncId(), newAction)); + + assert importedNewActionList != null; + + return Flux.fromIterable(importedNewActionList) + .flatMap(newAction -> { + NewPage parentPage = new NewPage(); + if (newAction.getUnpublishedAction() != null && newAction.getUnpublishedAction().getName() != null) { + parentPage = pageNameMap.get(newAction.getUnpublishedAction().getPageId()); + actionIdMap.put(newAction.getUnpublishedAction().getName() + parentPage.getId(), newAction.getId()); + newAction.getUnpublishedAction().setPageId(parentPage.getId()); + sanitizeDatasourceInActionDTO(newAction.getUnpublishedAction(), datasourceMap, pluginMap, organizationId); + } + + if (newAction.getPublishedAction() != null && newAction.getPublishedAction().getName() != null) { + parentPage = pageNameMap.get(newAction.getPublishedAction().getPageId()); + actionIdMap.put(newAction.getPublishedAction().getName() + parentPage.getId(), newAction.getId()); + newAction.getPublishedAction().setPageId(parentPage.getId()); + sanitizeDatasourceInActionDTO(newAction.getPublishedAction(), datasourceMap, pluginMap, organizationId); + } + + examplesOrganizationCloner.makePristine(newAction); + newAction.setOrganizationId(organizationId); + newAction.setApplicationId(importedApplication.getId()); + newAction.setPluginId(pluginMap.get(newAction.getPluginId())); + newActionService.generateAndSetActionPolicies(parentPage, newAction); + + // Check if the action has gitSyncId and if it's already in DB + if (newAction.getGitSyncId() != null + && savedActionsGitIdToActionsMap.containsKey(newAction.getGitSyncId())) { + + //Since the resource is already present in DB, just update resource + NewAction existingAction = savedActionsGitIdToActionsMap.get(newAction.getGitSyncId()); + newAction.setId(savedActionsGitIdToActionsMap.get(newAction.getGitSyncId()).getId()); + BeanCopyUtils.copyNewFieldValuesIntoOldObject(newAction, existingAction); + return newActionService.update(newAction.getId(), existingAction); + } + return newActionService.save(newAction); + }) + .map(newAction -> { + // Populate actionIdsMap to associate the appropriate actions to be run on page load + if (newAction.getUnpublishedAction() != null) { + ActionDTO unpublishedAction = newAction.getUnpublishedAction(); + actionIdMap.put( + actionIdMap.get(unpublishedAction.getName() + unpublishedAction.getPageId()), + newAction.getId() + ); + + if (unpublishedAction.getCollectionId() != null) { + unpublishedActionCollectionIdMap.putIfAbsent(unpublishedAction.getCollectionId(), new HashSet<>()); + final Set actionIds = unpublishedActionCollectionIdMap.get(unpublishedAction.getCollectionId()); + actionIds.add(newAction.getId()); + } + } + if (newAction.getPublishedAction() != null) { + ActionDTO publishedAction = newAction.getPublishedAction(); + actionIdMap.put( + actionIdMap.get(publishedAction.getName() + publishedAction.getPageId()), + newAction.getId() + ); + + if (publishedAction.getCollectionId() != null) { + publishedActionCollectionIdMap.putIfAbsent(publishedAction.getCollectionId(), new HashSet<>()); + final Set actionIds = publishedActionCollectionIdMap.get(publishedAction.getCollectionId()); + actionIds.add(newAction.getId()); + } + } + return newAction; + }) + .then() + .thenReturn(true) + .flatMap(x -> actionCollectionRepository.findByApplicationId(importedApplication.getId()) + .collectList()); + }) + .flatMap(existingActionCollections -> { + assert importedActionCollectionList != null; + + return Flux.fromIterable(importedActionCollectionList) + .flatMap(actionCollection -> { + final String actionCollectionId = actionCollection.getId(); + NewPage parentPage = new NewPage(); + final ActionCollectionDTO unpublishedCollection = actionCollection.getUnpublishedCollection(); + if (unpublishedCollection != null && unpublishedCollection.getName() != null) { + parentPage = pageNameMap.get(unpublishedCollection.getPageId()); + unpublishedCollection.setActionIds(unpublishedActionCollectionIdMap.get(actionCollection.getId())); + unpublishedCollection.setPageId(parentPage.getId()); + unpublishedCollection.setPluginId(pluginMap.get(unpublishedCollection.getPluginId())); + } + + final ActionCollectionDTO publishedCollection = actionCollection.getPublishedCollection(); + if (publishedCollection != null && publishedCollection.getName() != null) { + parentPage = pageNameMap.get(publishedCollection.getPageId()); + publishedCollection.setActionIds(publishedActionCollectionIdMap.get(actionCollection.getId())); + publishedCollection.setPageId(parentPage.getId()); + publishedCollection.setPluginId(pluginMap.get(publishedCollection.getPluginId())); + } + + examplesOrganizationCloner.makePristine(actionCollection); + actionCollection.setOrganizationId(organizationId); + actionCollection.setApplicationId(importedApplication.getId()); + actionCollectionService.generateAndSetPolicies(parentPage, actionCollection); + + return actionCollectionService.save(actionCollection) + .flatMapMany(createdActionCollection -> { + unpublishedActionCollectionIdMap + .getOrDefault(actionCollectionId, Set.of()) + .forEach(actionId -> { + unpublishedActionIdToCollectionIdMap.put(actionId, actionCollectionId); + }); + publishedActionCollectionIdMap + .getOrDefault(actionCollectionId, Set.of()) + .forEach(actionId -> { + publishedActionIdToCollectionIdMap.put(actionId, actionCollectionId); + }); + final HashSet actionIds = new HashSet<>(); + actionIds.addAll(unpublishedActionIdToCollectionIdMap.keySet()); + actionIds.addAll(publishedActionIdToCollectionIdMap.keySet()); + return Flux.fromIterable(actionIds); + }) + .flatMap(actionId -> newActionRepository.findById(actionId, AclPermission.MANAGE_ACTIONS)) + .map(newAction -> { + newAction.getUnpublishedAction().setCollectionId( + unpublishedActionIdToCollectionIdMap.getOrDefault(newAction.getId(), null)); + newAction.getPublishedAction().setCollectionId( + publishedActionIdToCollectionIdMap.getOrDefault(newAction.getId(), null)); + return newAction; + }) + .flatMap(newAction -> newActionService.update(newAction.getId(), newAction)) + .then() + .thenReturn(true); + }) + .then() + .thenReturn(true); + }) + .flatMap(ignored -> { + // Map layoutOnLoadActions ids with relevant actions + assert importedNewPageList != null; + importedNewPageList.forEach(page -> mapActionIdWithPageLayout(page, actionIdMap)); + return Flux.fromIterable(importedNewPageList) + .flatMap(newPageService::save) + .then(applicationService.update(importedApplication.getId(), importedApplication)); + }); } /** * This function will respond with unique suffixed number for the entity to avoid duplicate names + * * @param sourceEntity for which the suffixed number is required to avoid duplication - * @param orgId organisation in which entity should be searched + * @param orgId organisation in which entity should be searched * @return next possible number in case of duplication */ private Mono getUniqueSuffixForDuplicateNameEntity(BaseDomain sourceEntity, String orgId) { if (sourceEntity != null) { return sequenceService - .getNextAsSuffix(sourceEntity.getClass(), " for organization with _id : " + orgId) - .map(sequenceNumber -> { - // sequence number will be empty if no duplicate is found - return sequenceNumber.isEmpty() ? " #1" : " #" + sequenceNumber.trim(); - }); + .getNextAsSuffix(sourceEntity.getClass(), " for organization with _id : " + orgId) + .map(sequenceNumber -> { + // sequence number will be empty if no duplicate is found + return sequenceNumber.isEmpty() ? " #1" : " #" + sequenceNumber.trim(); + }); } return Mono.just(""); } /** * This function will set the mongoEscapedWidgets if present in the page along with setting the policies for the page - * @param pages pagelist extracted from the imported JSON file - * @param application saved application where pages needs to be added - * @param publishedMongoEscapedWidget widget list those needs to be escaped for published layout + * + * @param pages pagelist extracted from the imported JSON file + * @param application saved application where pages needs to be added + * @param publishedMongoEscapedWidget widget list those needs to be escaped for published layout * @param unpublishedMongoEscapedWidget widget list those needs to be escaped for unpublished layout * @return saved pages */ @@ -656,8 +779,8 @@ public class ImportExportApplicationService { Map> unpublishedMongoEscapedWidget) { Mono> existingPages = newPageService - .findNewPagesByApplicationId(application.getId(), AclPermission.MANAGE_PAGES) - .collectList(); + .findNewPagesByApplicationId(application.getId(), AclPermission.MANAGE_PAGES) + .collectList(); pages.forEach(newPage -> { String layoutId = new ObjectId().toString(); @@ -688,34 +811,35 @@ public class ImportExportApplicationService { Map savedPagesGitIdToPageMap = new HashMap<>(); existingSavedPages.stream() - .filter(newPage -> newPage.getGitSyncId() != null) - .forEach(newPage -> savedPagesGitIdToPageMap.put(newPage.getGitSyncId(), newPage)); + .filter(newPage -> newPage.getGitSyncId() != null) + .forEach(newPage -> savedPagesGitIdToPageMap.put(newPage.getGitSyncId(), newPage)); return Flux.fromIterable(pages) - .flatMap(newPage -> { - // Check if the page has gitSyncId and if it's already in DB - if (newPage.getGitSyncId() != null && savedPagesGitIdToPageMap.containsKey(newPage.getGitSyncId())) { - //Since the resource is already present in DB, just update resource - NewPage existingPage = savedPagesGitIdToPageMap.get(newPage.getGitSyncId()); - newPage.setId(savedPagesGitIdToPageMap.get(newPage.getGitSyncId()).getId()); - BeanCopyUtils.copyNewFieldValuesIntoOldObject(newPage, existingPage); - return newPageService.update(newPage.getId(), existingPage); - } - return newPageService.save(newPage); - }); + .flatMap(newPage -> { + // Check if the page has gitSyncId and if it's already in DB + if (newPage.getGitSyncId() != null && savedPagesGitIdToPageMap.containsKey(newPage.getGitSyncId())) { + //Since the resource is already present in DB, just update resource + NewPage existingPage = savedPagesGitIdToPageMap.get(newPage.getGitSyncId()); + newPage.setId(savedPagesGitIdToPageMap.get(newPage.getGitSyncId()).getId()); + BeanCopyUtils.copyNewFieldValuesIntoOldObject(newPage, existingPage); + return newPageService.update(newPage.getId(), existingPage); + } + return newPageService.save(newPage); + }); }); } /** * This function will be used to sanitise datasource within the actionDTO - * @param actionDTO for which the datasource needs to be sanitised as per import format expected - * @param datasourceMap datasource id to name map - * @param pluginMap plugin id to name map + * + * @param actionDTO for which the datasource needs to be sanitised as per import format expected + * @param datasourceMap datasource id to name map + * @param pluginMap plugin id to name map * @param organizationId organisation in which the application supposed to be imported * @return */ private String sanitizeDatasourceInActionDTO(ActionDTO actionDTO, Map datasourceMap, Map pluginMap, String organizationId) { - + if (actionDTO != null && actionDTO.getDatasource() != null) { Datasource ds = actionDTO.getDatasource(); @@ -756,7 +880,7 @@ public class ImportExportApplicationService { page.getPublishedPage().getLayouts().forEach(layout -> { if (layout.getLayoutOnLoadActions() != null) { layout.getLayoutOnLoadActions().forEach(onLoadAction -> onLoadAction - .forEach(actionDTO -> actionDTO.setId(actionIdMap.get(actionDTO.getId())))); + .forEach(actionDTO -> actionDTO.setId(actionIdMap.get(actionDTO.getId())))); } }); } @@ -764,9 +888,10 @@ public class ImportExportApplicationService { /** * This will check if the datasource is already present in the organization and create a new one if unable to find one + * * @param existingDatasourceFlux already present datasource in the organization - * @param datasource which will be checked against existing datasources - * @param organizationId organization where duplicate datasource should be checked + * @param datasource which will be checked against existing datasources + * @param organizationId organization where duplicate datasource should be checked * @return already present or brand new datasource depending upon the equality check */ private Mono createUniqueDatasourceIfNotPresent(Flux existingDatasourceFlux, @@ -781,11 +906,11 @@ public class ImportExportApplicationService { AuthenticationResponse authResponse = new AuthenticationResponse(); if (datasourceConfig != null && datasourceConfig.getAuthentication() != null) { BeanCopyUtils.copyNestedNonNullProperties( - datasourceConfig.getAuthentication().getAuthenticationResponse(), authResponse); + datasourceConfig.getAuthentication().getAuthenticationResponse(), authResponse); datasourceConfig.getAuthentication().setAuthenticationResponse(null); datasourceConfig.getAuthentication().setAuthenticationType(null); } - + return existingDatasourceFlux .map(ds -> { final DatasourceConfiguration dsAuthConfig = ds.getDatasourceConfiguration(); @@ -803,21 +928,22 @@ public class ImportExportApplicationService { } // No matching existing datasource found, so create a new one. return datasourceService - .findByNameAndOrganizationId(datasource.getName(), organizationId, AclPermission.MANAGE_DATASOURCES) - .flatMap(duplicateNameDatasource -> - getUniqueSuffixForDuplicateNameEntity(duplicateNameDatasource, organizationId) - ) - .map(suffix -> { - datasource.setName(datasource.getName() + suffix); - return datasource; - }) - .then(datasourceService.create(datasource)); + .findByNameAndOrganizationId(datasource.getName(), organizationId, AclPermission.MANAGE_DATASOURCES) + .flatMap(duplicateNameDatasource -> + getUniqueSuffixForDuplicateNameEntity(duplicateNameDatasource, organizationId) + ) + .map(suffix -> { + datasource.setName(datasource.getName() + suffix); + return datasource; + }) + .then(datasourceService.create(datasource)); })); } /** * Here we will be rehydrating the sensitive fields like password, secrets etc. in datasource while importing the application - * @param datasource for which sensitive fields should be rehydrated + * + * @param datasource for which sensitive fields should be rehydrated * @param decryptedFields sensitive fields * @return updated datasource with rehydrated sensitive fields */ @@ -853,6 +979,7 @@ public class ImportExportApplicationService { /** * This will be used to dehydrate sensitive fields from the datasource while exporting the application + * * @param datasource entity from which sensitive fields need to be dehydrated * @return sensitive fields which then will be deserialized and exported in JSON file */ @@ -863,8 +990,8 @@ public class ImportExportApplicationService { if (authentication != null) { DecryptedSensitiveFields dsDecryptedFields = authentication.getAuthenticationResponse() == null - ? new DecryptedSensitiveFields() - : new DecryptedSensitiveFields(authentication.getAuthenticationResponse()); + ? new DecryptedSensitiveFields() + : new DecryptedSensitiveFields(authentication.getAuthenticationResponse()); if (authentication instanceof DBAuth) { DBAuth auth = (DBAuth) authentication; diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceTest.java index 71c99bf69d..ed93840b5a 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceTest.java @@ -183,7 +183,6 @@ public class ActionCollectionServiceTest { .build(); ActionCollectionDTO actionCollectionDTO = new ActionCollectionDTO(); - actionCollectionDTO.setName("validActionCollection"); actionCollectionDTO.setName("testActionCollection"); actionCollectionDTO.setApplicationId(testApp.getId()); actionCollectionDTO.setOrganizationId(testApp.getOrganizationId()); diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java index ad52f36718..0e13a5a631 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java @@ -2,16 +2,17 @@ package com.appsmith.server.solutions; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.DBAuth; +import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DecryptedSensitiveFields; import com.appsmith.external.models.Policy; import com.appsmith.external.models.Property; import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.SerialiseApplicationObjective; +import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationJson; import com.appsmith.server.domains.ApplicationPage; -import com.appsmith.external.models.Datasource; import com.appsmith.server.domains.GitApplicationMetadata; import com.appsmith.server.domains.Layout; import com.appsmith.server.domains.NewAction; @@ -19,6 +20,7 @@ import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.PluginType; +import com.appsmith.server.dtos.ActionCollectionDTO; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.exceptions.AppsmithError; @@ -27,10 +29,12 @@ import com.appsmith.server.helpers.MockPluginExecutor; import com.appsmith.server.helpers.PluginExecutorHelper; import com.appsmith.server.repositories.NewPageRepository; import com.appsmith.server.repositories.PluginRepository; +import com.appsmith.server.services.ActionCollectionService; import com.appsmith.server.services.ApplicationPageService; import com.appsmith.server.services.ApplicationService; import com.appsmith.server.services.DatasourceService; import com.appsmith.server.services.LayoutActionService; +import com.appsmith.server.services.LayoutCollectionService; import com.appsmith.server.services.NewActionService; import com.appsmith.server.services.NewPageService; import com.appsmith.server.services.OrganizationService; @@ -70,6 +74,7 @@ import java.util.Map; import java.util.Set; import static com.appsmith.server.acl.AclPermission.EXPORT_APPLICATIONS; +import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS; import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS; import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES; import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES; @@ -116,10 +121,16 @@ public class ImportExportApplicationServiceTests { @Autowired private LayoutActionService layoutActionService; - + @Autowired private NewPageRepository newPageRepository; + @Autowired + private LayoutCollectionService layoutCollectionService; + + @Autowired + private ActionCollectionService actionCollectionService; + @MockBean private PluginExecutorHelper pluginExecutorHelper; @@ -127,8 +138,10 @@ public class ImportExportApplicationServiceTests { private Plugin installedPlugin; private String orgId; private String testAppId; + private Datasource jsDatasource; private Map datasourceMap = new HashMap<>(); - + private Plugin installedJsPlugin; + private Flux getActionsInApplication(Application application) { return newPageService // fetch the unpublished pages @@ -178,6 +191,13 @@ public class ImportExportApplicationServiceTests { auth.setPassword("awesome-password"); ds2.getDatasourceConfiguration().setAuthentication(auth); + jsDatasource = new Datasource(); + jsDatasource.setName("Default JS datasource"); + jsDatasource.setOrganizationId(orgId); + installedJsPlugin = pluginRepository.findByPackageName("installed-js-plugin").block(); + assert installedJsPlugin != null; + jsDatasource.setPluginId(installedJsPlugin.getId()); + datasourceMap.put("DS1", ds1); datasourceMap.put("DS2", ds2); } @@ -347,7 +367,21 @@ public class ImportExportApplicationServiceTests { action.setActionConfiguration(actionConfiguration); action.setDatasource(ds2); - return layoutActionService.createSingleAction(action) + ActionCollectionDTO actionCollectionDTO1 = new ActionCollectionDTO(); + actionCollectionDTO1.setName("testCollection1"); + actionCollectionDTO1.setPageId(testPage.getId()); + actionCollectionDTO1.setApplicationId(testApp.getId()); + actionCollectionDTO1.setOrganizationId(testApp.getOrganizationId()); + 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) + .then(layoutActionService.createSingleAction(action)) .flatMap(createdAction -> newActionService.findById(createdAction.getId(), READ_ACTIONS)) .flatMap(newAction -> newActionService.generateActionByViewMode(newAction, false)) .then(importExportApplicationService.exportApplicationById(testApp.getId())); @@ -360,6 +394,7 @@ public class ImportExportApplicationServiceTests { Application exportedApp = applicationJson.getExportedApplication(); List pageList = applicationJson.getPageList(); List actionList = applicationJson.getActionList(); + List actionCollectionList = applicationJson.getActionCollectionList(); List datasourceList = applicationJson.getDatasourceList(); NewPage defaultPage = pageList.get(0); @@ -367,7 +402,7 @@ public class ImportExportApplicationServiceTests { assertThat(exportedApp.getName()).isEqualTo(testApplication.getName()); assertThat(exportedApp.getOrganizationId()).isNull(); assertThat(exportedApp.getPages()).isNull(); - + assertThat(exportedApp.getPolicies()).hasSize(0); assertThat(pageList).hasSize(1); @@ -377,7 +412,10 @@ public class ImportExportApplicationServiceTests { assertThat(defaultPage.getPolicies()).isEmpty(); assertThat(actionList.isEmpty()).isFalse(); - NewAction validAction = actionList.get(0); + assertThat(actionList).hasSize(2); + NewAction validAction = actionList.get(0).getPluginType().equals(PluginType.JS) ? + actionList.get(1) : + actionList.get(0); assertThat(validAction.getApplicationId()).isNull(); assertThat(validAction.getPluginId()).isEqualTo(installedPlugin.getPackageName()); assertThat(validAction.getPluginType()).isEqualTo(PluginType.API); @@ -388,6 +426,18 @@ public class ImportExportApplicationServiceTests { assertThat(unpublishedAction.getPageId()).isEqualTo(defaultPage.getUnpublishedPage().getName()); assertThat(unpublishedAction.getDatasource().getPluginId()).isEqualTo(installedPlugin.getPackageName()); + assertThat(actionCollectionList.isEmpty()).isFalse(); + assertThat(actionCollectionList).hasSize(1); + final ActionCollection actionCollection = actionCollectionList.get(0); + assertThat(actionCollection.getApplicationId()).isNull(); + assertThat(actionCollection.getOrganizationId()).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); Datasource datasource = datasourceList.get(0); assertThat(datasource.getOrganizationId()).isNull(); @@ -593,17 +643,19 @@ public class ImportExportApplicationServiceTests { StepVerifier .create(resultMono .flatMap(application -> Mono.zip( - Mono.just(application), - datasourceService.findAllByOrganizationId(application.getOrganizationId(), MANAGE_DATASOURCES).collectList(), - getActionsInApplication(application).collectList(), - newPageService.findByApplicationId(application.getId(), MANAGE_PAGES, false).collectList() + Mono.just(application), + datasourceService.findAllByOrganizationId(application.getOrganizationId(), MANAGE_DATASOURCES).collectList(), + getActionsInApplication(application).collectList(), + newPageService.findByApplicationId(application.getId(), MANAGE_PAGES, false).collectList(), + actionCollectionService.findAllByApplicationIdAndViewMode(application.getId(), false, MANAGE_ACTIONS, null).collectList() ))) .assertNext(tuple -> { final Application application = tuple.getT1(); final List datasourceList = tuple.getT2(); final List actionDTOS = tuple.getT3(); final List pageList = tuple.getT4(); - + final List actionCollectionList = tuple.getT5(); + assertThat(application.getName()).isEqualTo("valid_application"); assertThat(application.getOrganizationId()).isNotNull(); assertThat(application.getPages()).hasSize(2); @@ -624,20 +676,25 @@ public class ImportExportApplicationServiceTests { assertThat(auth.getUsername()).isNotNull(); } }); - + assertThat(actionDTOS).isNotEmpty(); actionDTOS.forEach(actionDTO -> { assertThat(actionDTO.getPageId()).isNotEqualTo(pageList.get(0).getName()); - + }); - + + assertThat(actionCollectionList).isNotEmpty(); + actionCollectionList.forEach(actionCollection -> { + assertThat(actionCollection.getUnpublishedCollection().getPageId()).isNotEqualTo(pageList.get(0).getName()); + }); + assertThat(pageList).hasSize(2); - + ApplicationPage defaultAppPage = application.getPages() - .stream() - .filter(ApplicationPage::getIsDefault) - .findFirst() - .orElse(null); + .stream() + .filter(ApplicationPage::getIsDefault) + .findFirst() + .orElse(null); assertThat(defaultAppPage).isNotNull(); PageDTO defaultPageDTO = pageList.stream() diff --git a/app/server/appsmith-server/src/test/resources/test_assets/ImportExportServiceTest/valid-application.json b/app/server/appsmith-server/src/test/resources/test_assets/ImportExportServiceTest/valid-application.json index 7d62385c66..c8261caf1b 100644 --- a/app/server/appsmith-server/src/test/resources/test_assets/ImportExportServiceTest/valid-application.json +++ b/app/server/appsmith-server/src/test/resources/test_assets/ImportExportServiceTest/valid-application.json @@ -1,553 +1,632 @@ { - "exportedApplication": { + "exportedApplication": { + "userPermissions": [ + "canComment:applications", + "manage:applications", + "read:applications", + "publish:applications", + "makePublic:applications" + ], + "name": "valid_application", + "isPublic": false, + "appIsExample": false, + "color": "#EA6179", + "icon": "medical", + "new": true + }, + "datasourceList": [ + { "userPermissions": [ - "canComment:applications", - "manage:applications", - "read:applications", - "publish:applications", - "makePublic:applications" + "execute:datasources", + "manage:datasources", + "read:datasources" ], - "name": "valid_application", - "isPublic": false, - "appIsExample": false, - "color": "#EA6179", - "icon": "medical", + "name": "db-auth", + "pluginId": "mongo-plugin", + "gitSyncId": "datasource1_git", + "datasourceConfiguration": { + "connection": { + "mode": "READ_WRITE", + "type": "REPLICA_SET", + "ssl": { + "authType": "DEFAULT" + } + }, + "endpoints": [ + { + "host": "db-auth-uri.net" + } + ], + "sshProxyEnabled": false, + "properties": [ + { + "key": "Use Mongo Connection String URI", + "value": "No" + } + ] + }, + "invalids": [], + "isValid": true, "new": true }, - "datasourceList": [ - { - "userPermissions": [ - "execute:datasources", - "manage:datasources", - "read:datasources" - ], - "name": "db-auth", - "pluginId": "mongo-plugin", - "gitSyncId": "datasource1_git", - "datasourceConfiguration": { - "connection": { - "mode": "READ_WRITE", - "type": "REPLICA_SET", - "ssl": { - "authType": "DEFAULT" - } + { + "userPermissions": [ + "execute:datasources", + "manage:datasources", + "read:datasources" + ], + "name": "api_ds_wo_auth", + "pluginId": "restapi-plugin", + "gitSyncId": "datasource2_git", + "datasourceConfiguration": { + "sshProxyEnabled": false, + "properties": [ + { + "key": "isSendSessionEnabled", + "value": "N" }, - "endpoints": [ + { + "key": "sessionSignatureKey", + "value": "" + } + ], + "url": "https://api-ds-wo-auth-uri.com", + "headers": [] + }, + "invalids": [], + "isValid": true, + "new": true + } + ], + "pageList": [ + { + "userPermissions": [ + "read:pages", + "manage:pages" + ], + "gitSyncId": "page1_git", + "applicationId": "valid_application", + "unpublishedPage": { + "name": "Page1", + "layouts": [ + { + "id": "60aca056136c4b7178f67906", + "userPermissions": [], + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1280, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 800, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 4, + "minHeight": 840, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "widgetName": "Table1", + "columnOrder": [ + "_id", + "username", + "active" + ], + "dynamicPropertyPathList": [], + "topRow": 4, + "bottomRow": 15, + "parentRowSpace": 40, + "type": "TABLE_WIDGET", + "parentColumnSpace": 77.5, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [ + { + "key": "tableData" + }, + { + "key": "primaryColumns._id.computedValue" + }, + { + "key": "primaryColumns.username.computedValue" + }, + { + "key": "primaryColumns.active.computedValue" + } + ], + "leftColumn": 0, + "primaryColumns": { + "appsmith_mongo_escape_id": { + "isDerived": false, + "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow._id ))}}", + "textSize": "PARAGRAPH", + "index": 4, + "isVisible": true, + "label": "_id", + "columnType": "text", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "_id", + "verticalAlignment": "CENTER" + }, + "active": { + "isDerived": false, + "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.active ))}}", + "textSize": "PARAGRAPH", + "index": 8, + "isVisible": true, + "label": "active", + "columnType": "text", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "active", + "verticalAlignment": "CENTER" + }, + "username": { + "isDerived": false, + "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.username ))}}", + "textSize": "PARAGRAPH", + "index": 7, + "isVisible": true, + "label": "username", + "columnType": "text", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "username", + "verticalAlignment": "CENTER" + } + }, + "derivedColumns": {}, + "rightColumn": 8, + "textSize": "PARAGRAPH", + "widgetId": "aisibaxwhb", + "tableData": "{{get_users.data}}", + "isVisible": true, + "label": "Data", + "searchKey": "", + "version": 1, + "parentId": "0", + "isLoading": false, + "horizontalAlignment": "LEFT", + "verticalAlignment": "CENTER", + "columnSizeMap": { + "task": 245, + "step": 62, + "status": 75 + } + }, + { + "widgetName": "Form1", + "backgroundColor": "white", + "rightColumn": 16, + "widgetId": "ut3l54pzqw", + "topRow": 4, + "bottomRow": 11, + "parentRowSpace": 40, + "isVisible": true, + "type": "FORM_WIDGET", + "parentId": "0", + "isLoading": false, + "parentColumnSpace": 77.5, + "leftColumn": 9, + "children": [ + { + "widgetName": "Canvas1", + "rightColumn": 542.5, + "detachFromLayout": true, + "widgetId": "mcsltg1l0j", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 320, + "parentRowSpace": 1, + "isVisible": true, + "canExtend": false, + "type": "CANVAS_WIDGET", + "version": 1, + "parentId": "ut3l54pzqw", + "minHeight": 520, + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "children": [ + { + "widgetName": "Text1", + "rightColumn": 6, + "textAlign": "LEFT", + "widgetId": "7b4x786lxp", + "topRow": 0, + "bottomRow": 1, + "isVisible": true, + "fontStyle": "BOLD", + "type": "TEXT_WIDGET", + "textColor": "#231F20", + "version": 1, + "parentId": "mcsltg1l0j", + "isLoading": false, + "leftColumn": 0, + "fontSize": "HEADING1", + "text": "Form" + }, + { + "widgetName": "Text2", + "rightColumn": 16, + "textAlign": "LEFT", + "widgetId": "d0axuxiosp", + "topRow": 3, + "bottomRow": 6, + "parentRowSpace": 40, + "isVisible": true, + "fontStyle": "BOLD", + "type": "TEXT_WIDGET", + "textColor": "#231F20", + "version": 1, + "parentId": "mcsltg1l0j", + "isLoading": false, + "parentColumnSpace": 31.40625, + "dynamicTriggerPathList": [], + "leftColumn": 0, + "dynamicBindingPathList": [ + { + "key": "text" + } + ], + "fontSize": "PARAGRAPH2", + "text": "{{api_wo_auth.data.body}}" + }, + { + "widgetName": "Text3", + "rightColumn": 4, + "textAlign": "LEFT", + "widgetId": "lmfer0622c", + "topRow": 2, + "bottomRow": 3, + "parentRowSpace": 40, + "isVisible": true, + "fontStyle": "BOLD", + "type": "TEXT_WIDGET", + "textColor": "#231F20", + "version": 1, + "parentId": "mcsltg1l0j", + "isLoading": false, + "parentColumnSpace": 31.40625, + "dynamicTriggerPathList": [], + "leftColumn": 0, + "dynamicBindingPathList": [ + { + "key": "text" + } + ], + "fontSize": "PARAGRAPH", + "text": "{{api_wo_auth.data.id}}" + } + ] + } + ] + } + ] + }, + "layoutOnLoadActions": [ + [ + { + "id": "60aca24c136c4b7178f6790d", + "name": "api_wo_auth", + "pluginType": "API", + "jsonPathKeys": [], + "timeoutInMillisecond": 10000 + }, + { + "id": "60aca092136c4b7178f6790a", + "name": "get_users", + "pluginType": "DB", + "jsonPathKeys": [], + "timeoutInMillisecond": 10000 + } + ] + ], + "new": false + } + ], + "userPermissions": [] + }, + "publishedPage": { + "name": "Page1", + "layouts": [ + { + "id": "60aca056136c4b7178f67906", + "userPermissions": [], + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1224, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1254, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 4, + "minHeight": 1292, + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [] + }, + "new": false + } + ], + "userPermissions": [] + }, + "new": true + }, + { + "userPermissions": [ + "read:pages", + "manage:pages" + ], + "gitSyncId": "page2_git", + "applicationId": "valid_application", + "unpublishedPage": { + "name": "Page2", + "layouts": [ + { + "id": "60aca056136c4b7178f67999", + "userPermissions": [], + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1280, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 800, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 4, + "minHeight": 840, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [] + }, + "layoutOnLoadActions": [ + [] + ], + "new": false + } + ], + "userPermissions": [] + }, + "new": true + } + ], + "actionList": [ + { + "id": "60aca092136c4b7178f6790a", + "userPermissions": [], + "applicationId": "valid_application", + "pluginType": "DB", + "pluginId": "mongo-plugin", + "gitSyncId": "action1_git", + "unpublishedAction": { + "name": "get_users", + "datasource": { + "id": "db-auth", + "userPermissions": [], + "isValid": true, + "new": false + }, + "pageId": "Page1", + "actionConfiguration": { + "timeoutInMillisecond": 10000, + "paginationType": "NONE", + "encodeParamsToggle": true, + "body": "{\n \"find\": \"users\",\n \"sort\": {\n \"id\": 1\n },\n \"limit\": 10\n}", + "pluginSpecifiedTemplates": [ { - "host": "db-auth-uri.net" - } - ], - "sshProxyEnabled": false, - "properties": [ - { - "key": "Use Mongo Connection String URI", - "value": "No" + "value": false } ] }, - "invalids": [], + "executeOnLoad": true, + "dynamicBindingPathList": [], "isValid": true, - "new": true + "invalids": [], + "jsonPathKeys": [], + "confirmBeforeExecute": false, + "userPermissions": [] }, - { - "userPermissions": [ - "execute:datasources", - "manage:datasources", - "read:datasources" - ], - "name": "api_ds_wo_auth", - "pluginId": "restapi-plugin", - "gitSyncId": "datasource2_git", - "datasourceConfiguration": { - "sshProxyEnabled": false, - "properties": [ + "publishedAction": { + "datasource": { + "userPermissions": [], + "isValid": true, + "new": true + }, + "confirmBeforeExecute": false, + "userPermissions": [] + }, + "new": false + }, + { + "id": "60aca24c136c4b7178f6790d", + "userPermissions": [], + "applicationId": "valid_application", + "pluginType": "API", + "pluginId": "restapi-plugin", + "gitSyncId": "action2_git", + "unpublishedAction": { + "name": "api_wo_auth", + "datasource": { + "id": "api_ds_wo_auth", + "userPermissions": [], + "isValid": true, + "new": false + }, + "pageId": "Page1", + "actionConfiguration": { + "timeoutInMillisecond": 10000, + "paginationType": "NONE", + "path": "/params", + "headers": [ { - "key": "isSendSessionEnabled", - "value": "N" + "key": "", + "value": "" }, { - "key": "sessionSignatureKey", + "key": "", "value": "" } ], - "url": "https://api-ds-wo-auth-uri.com", - "headers": [] + "encodeParamsToggle": true, + "queryParameters": [], + "body": "", + "httpMethod": "GET", + "pluginSpecifiedTemplates": [ + { + "value": false + } + ] }, - "invalids": [], + "executeOnLoad": true, + "dynamicBindingPathList": [], "isValid": true, - "new": true - } - ], - "pageList": [ - { - "userPermissions": [ - "read:pages", - "manage:pages" - ], - "gitSyncId": "page1_git", - "applicationId": "valid_application", - "unpublishedPage": { - "name": "Page1", - "layouts": [ - { - "id": "60aca056136c4b7178f67906", - "userPermissions": [], - "dsl": { - "widgetName": "MainContainer", - "backgroundColor": "none", - "rightColumn": 1280, - "snapColumns": 16, - "detachFromLayout": true, - "widgetId": "0", - "topRow": 0, - "bottomRow": 800, - "containerStyle": "none", - "snapRows": 33, - "parentRowSpace": 1, - "type": "CANVAS_WIDGET", - "canExtend": true, - "version": 4, - "minHeight": 840, - "parentColumnSpace": 1, - "dynamicTriggerPathList": [], - "dynamicBindingPathList": [], - "leftColumn": 0, - "children": [ - { - "widgetName": "Table1", - "columnOrder": [ - "_id", - "username", - "active" - ], - "dynamicPropertyPathList": [], - "topRow": 4, - "bottomRow": 15, - "parentRowSpace": 40, - "type": "TABLE_WIDGET", - "parentColumnSpace": 77.5, - "dynamicTriggerPathList": [], - "dynamicBindingPathList": [ - { - "key": "tableData" - }, - { - "key": "primaryColumns._id.computedValue" - }, - { - "key": "primaryColumns.username.computedValue" - }, - { - "key": "primaryColumns.active.computedValue" - } - ], - "leftColumn": 0, - "primaryColumns": { - "appsmith_mongo_escape_id": { - "isDerived": false, - "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow._id ))}}", - "textSize": "PARAGRAPH", - "index": 4, - "isVisible": true, - "label": "_id", - "columnType": "text", - "horizontalAlignment": "LEFT", - "width": 150, - "enableFilter": true, - "enableSort": true, - "id": "_id", - "verticalAlignment": "CENTER" - }, - "active": { - "isDerived": false, - "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.active ))}}", - "textSize": "PARAGRAPH", - "index": 8, - "isVisible": true, - "label": "active", - "columnType": "text", - "horizontalAlignment": "LEFT", - "width": 150, - "enableFilter": true, - "enableSort": true, - "id": "active", - "verticalAlignment": "CENTER" - }, - "username": { - "isDerived": false, - "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.username ))}}", - "textSize": "PARAGRAPH", - "index": 7, - "isVisible": true, - "label": "username", - "columnType": "text", - "horizontalAlignment": "LEFT", - "width": 150, - "enableFilter": true, - "enableSort": true, - "id": "username", - "verticalAlignment": "CENTER" - } - }, - "derivedColumns": {}, - "rightColumn": 8, - "textSize": "PARAGRAPH", - "widgetId": "aisibaxwhb", - "tableData": "{{get_users.data}}", - "isVisible": true, - "label": "Data", - "searchKey": "", - "version": 1, - "parentId": "0", - "isLoading": false, - "horizontalAlignment": "LEFT", - "verticalAlignment": "CENTER", - "columnSizeMap": { - "task": 245, - "step": 62, - "status": 75 - } - }, - { - "widgetName": "Form1", - "backgroundColor": "white", - "rightColumn": 16, - "widgetId": "ut3l54pzqw", - "topRow": 4, - "bottomRow": 11, - "parentRowSpace": 40, - "isVisible": true, - "type": "FORM_WIDGET", - "parentId": "0", - "isLoading": false, - "parentColumnSpace": 77.5, - "leftColumn": 9, - "children": [ - { - "widgetName": "Canvas1", - "rightColumn": 542.5, - "detachFromLayout": true, - "widgetId": "mcsltg1l0j", - "containerStyle": "none", - "topRow": 0, - "bottomRow": 320, - "parentRowSpace": 1, - "isVisible": true, - "canExtend": false, - "type": "CANVAS_WIDGET", - "version": 1, - "parentId": "ut3l54pzqw", - "minHeight": 520, - "isLoading": false, - "parentColumnSpace": 1, - "leftColumn": 0, - "children": [ - { - "widgetName": "Text1", - "rightColumn": 6, - "textAlign": "LEFT", - "widgetId": "7b4x786lxp", - "topRow": 0, - "bottomRow": 1, - "isVisible": true, - "fontStyle": "BOLD", - "type": "TEXT_WIDGET", - "textColor": "#231F20", - "version": 1, - "parentId": "mcsltg1l0j", - "isLoading": false, - "leftColumn": 0, - "fontSize": "HEADING1", - "text": "Form" - }, - { - "widgetName": "Text2", - "rightColumn": 16, - "textAlign": "LEFT", - "widgetId": "d0axuxiosp", - "topRow": 3, - "bottomRow": 6, - "parentRowSpace": 40, - "isVisible": true, - "fontStyle": "BOLD", - "type": "TEXT_WIDGET", - "textColor": "#231F20", - "version": 1, - "parentId": "mcsltg1l0j", - "isLoading": false, - "parentColumnSpace": 31.40625, - "dynamicTriggerPathList": [], - "leftColumn": 0, - "dynamicBindingPathList": [ - { - "key": "text" - } - ], - "fontSize": "PARAGRAPH2", - "text": "{{api_wo_auth.data.body}}" - }, - { - "widgetName": "Text3", - "rightColumn": 4, - "textAlign": "LEFT", - "widgetId": "lmfer0622c", - "topRow": 2, - "bottomRow": 3, - "parentRowSpace": 40, - "isVisible": true, - "fontStyle": "BOLD", - "type": "TEXT_WIDGET", - "textColor": "#231F20", - "version": 1, - "parentId": "mcsltg1l0j", - "isLoading": false, - "parentColumnSpace": 31.40625, - "dynamicTriggerPathList": [], - "leftColumn": 0, - "dynamicBindingPathList": [ - { - "key": "text" - } - ], - "fontSize": "PARAGRAPH", - "text": "{{api_wo_auth.data.id}}" - } - ] - } - ] - } - ] - }, - "layoutOnLoadActions": [ - [ - { - "id": "60aca24c136c4b7178f6790d", - "name": "api_wo_auth", - "pluginType": "API", - "jsonPathKeys": [], - "timeoutInMillisecond": 10000 - }, - { - "id": "60aca092136c4b7178f6790a", - "name": "get_users", - "pluginType": "DB", - "jsonPathKeys": [], - "timeoutInMillisecond": 10000 - } - ] - ], - "new": false - } - ], - "userPermissions": [] - }, - "publishedPage": { - "name": "Page1", - "layouts": [ - { - "id": "60aca056136c4b7178f67906", - "userPermissions": [], - "dsl": { - "widgetName": "MainContainer", - "backgroundColor": "none", - "rightColumn": 1224, - "snapColumns": 16, - "detachFromLayout": true, - "widgetId": "0", - "topRow": 0, - "bottomRow": 1254, - "containerStyle": "none", - "snapRows": 33, - "parentRowSpace": 1, - "type": "CANVAS_WIDGET", - "canExtend": true, - "version": 4, - "minHeight": 1292, - "parentColumnSpace": 1, - "dynamicBindingPathList": [], - "leftColumn": 0, - "children": [] - }, - "new": false - } - ], - "userPermissions": [] - }, - "new": true + "invalids": [], + "jsonPathKeys": [], + "confirmBeforeExecute": false, + "userPermissions": [] }, - { - "userPermissions": [ - "read:pages", - "manage:pages" - ], - "gitSyncId": "page2_git", - "applicationId": "valid_application", - "unpublishedPage": { - "name": "Page2", - "layouts": [ - { - "id": "60aca056136c4b7178f67999", - "userPermissions": [], - "dsl": { - "widgetName": "MainContainer", - "backgroundColor": "none", - "rightColumn": 1280, - "snapColumns": 16, - "detachFromLayout": true, - "widgetId": "0", - "topRow": 0, - "bottomRow": 800, - "containerStyle": "none", - "snapRows": 33, - "parentRowSpace": 1, - "type": "CANVAS_WIDGET", - "canExtend": true, - "version": 4, - "minHeight": 840, - "parentColumnSpace": 1, - "dynamicTriggerPathList": [], - "dynamicBindingPathList": [], - "leftColumn": 0, - "children": [] - }, - "layoutOnLoadActions": [[]], - "new": false - } - ], - "userPermissions": [] - }, - "new": true - } - ], - "actionList": [ - { - "id": "60aca092136c4b7178f6790a", - "userPermissions": [], - "applicationId": "valid_application", - "pluginType": "DB", - "pluginId": "mongo-plugin", - "gitSyncId": "action1_git", - "unpublishedAction": { - "name": "get_users", - "datasource": { - "id": "db-auth", - "userPermissions": [], - "isValid": true, - "new": false - }, - "pageId": "Page1", - "actionConfiguration": { - "timeoutInMillisecond": 10000, - "paginationType": "NONE", - "encodeParamsToggle": true, - "body": "{\n \"find\": \"users\",\n \"sort\": {\n \"id\": 1\n },\n \"limit\": 10\n}", - "pluginSpecifiedTemplates": [ - { - "value": false - } - ] - }, - "executeOnLoad": true, - "dynamicBindingPathList": [], + "publishedAction": { + "datasource": { + "userPermissions": [], "isValid": true, - "invalids": [], - "jsonPathKeys": [], - "confirmBeforeExecute": false, - "userPermissions": [] + "new": true }, - "publishedAction": { - "datasource": { - "userPermissions": [], - "isValid": true, - "new": true - }, - "confirmBeforeExecute": false, - "userPermissions": [] - }, - "new": false + "confirmBeforeExecute": false, + "userPermissions": [] }, - { - "id": "60aca24c136c4b7178f6790d", - "userPermissions": [], - "applicationId": "valid_application", - "pluginType": "API", - "pluginId": "restapi-plugin", - "gitSyncId": "action2_git", - "unpublishedAction": { - "name": "api_wo_auth", - "datasource": { - "id": "api_ds_wo_auth", - "userPermissions": [], - "isValid": true, - "new": false - }, - "pageId": "Page1", - "actionConfiguration": { - "timeoutInMillisecond": 10000, - "paginationType": "NONE", - "path": "/params", - "headers": [ - { - "key": "", - "value": "" - }, - { - "key": "", - "value": "" - } - ], - "encodeParamsToggle": true, - "queryParameters": [], - "body": "", - "httpMethod": "GET", - "pluginSpecifiedTemplates": [ - { - "value": false - } - ] - }, - "executeOnLoad": true, - "dynamicBindingPathList": [], + "new": false + }, + { + "id": "61518b8548b30155375f5275", + "userPermissions": [ + "read:actions", + "execute:actions", + "manage:actions" + ], + "pluginType": "JS", + "pluginId": "js-plugin", + "unpublishedAction": { + "name": "run", + "fullyQualifiedName": "JSObject1.run", + "datasource": { + "userPermissions": [], + "name": "UNUSED_DATASOURCE", + "pluginId": "js-plugin", "isValid": true, - "invalids": [], - "jsonPathKeys": [], - "confirmBeforeExecute": false, - "userPermissions": [] + "new": true }, - "publishedAction": { - "datasource": { - "userPermissions": [], - "isValid": true, - "new": true - }, - "confirmBeforeExecute": false, - "userPermissions": [] + "pageId": "Page1", + "collectionId": "61518b8548b30155375f5276", + "actionConfiguration": { + "timeoutInMillisecond": 10000, + "paginationType": "NONE", + "encodeParamsToggle": true, + "body": "() => {\n\t\t//write code here\n\t\treturn \"Hi\"\n\t}", + "jsArguments": [], + "isAsync": false }, - "new": false - } - ], - "decryptedFields": { - "db-auth": { - "password": "CreativePassword", - "authType": "com.appsmith.external.models.DBAuth", - "dbAuth": { - "authenticationType": "dbAuth", - "authType": "SCRAM_SHA_1", - "username": "CreativeUser", - "databaseName": "db-name" - } - } - }, - "publishedDefaultPageName": "Page1", - "unpublishedDefaultPageName": "Page1", - "publishedLayoutmongoEscapedWidgets": { - "60aca056136c4b7178f67906": [ - "Table1" - ] - }, - "unpublishedLayoutmongoEscapedWidgets": { - "60aca056136c4b7178f67906": [ - "Table1" - ] + "executeOnLoad": false, + "isValid": true, + "invalids": [], + "jsonPathKeys": [ + "() => {\n\t\t//write code here\n\t\treturn \"Hi\"\n\t}" + ], + "confirmBeforeExecute": false, + "userPermissions": [], + "validName": "JSObject1.run" + }, + "publishedAction": { + "datasource": { + "userPermissions": [], + "isValid": true, + "new": true + }, + "confirmBeforeExecute": false, + "userPermissions": [] + }, + "gitSyncId": "614b5f42a25cb80bca4ccf35_2021-09-27T09:14:45.330186Z", + "new": false } + ], + "actionCollectionList": [ + { + "id": "61518b8548b30155375f5276", + "userPermissions": [ + "read:actions", + "execute:actions", + "manage:actions" + ], + "unpublishedCollection": { + "name": "JSObject1", + "pageId": "Page1", + "pluginId": "js-plugin", + "pluginType": "JS", + "actions": [], + "archivedActions": [], + "body": "export default {\n\tresults: [],\n\trun: () => {\n\t\t//write code here\n\t\treturn \"Hi\"\n\t}\n}", + "variables": [ + { + "name": "results", + "value": [] + } + ] + }, + "new": false + } + ], + "decryptedFields": { + "db-auth": { + "password": "CreativePassword", + "authType": "com.appsmith.external.models.DBAuth", + "dbAuth": { + "authenticationType": "dbAuth", + "authType": "SCRAM_SHA_1", + "username": "CreativeUser", + "databaseName": "db-name" + } + } + }, + "publishedDefaultPageName": "Page1", + "unpublishedDefaultPageName": "Page1", + "publishedLayoutmongoEscapedWidgets": { + "60aca056136c4b7178f67906": [ + "Table1" + ] + }, + "unpublishedLayoutmongoEscapedWidgets": { + "60aca056136c4b7178f67906": [ + "Table1" + ] + } } \ No newline at end of file