feat: Import an template inside an existing Application (#12507)
This PR adds the feature to import a template inside an application.
This commit is contained in:
parent
144c999d1c
commit
da2455bb5c
|
|
@ -1,5 +1,6 @@
|
|||
package com.appsmith.server.controllers.ce;
|
||||
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.dtos.ApplicationTemplate;
|
||||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
|
|
@ -9,6 +10,8 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -58,4 +61,14 @@ public class ApplicationTemplateControllerCE {
|
|||
return applicationTemplateService.getRecentlyUsedTemplates().collectList()
|
||||
.map(templates -> new ResponseDTO<>(HttpStatus.OK.value(), templates, null));
|
||||
}
|
||||
|
||||
@PostMapping("{templateId}/merge/{applicationId}/{organizationId}")
|
||||
public Mono<ResponseDTO<Application>> mergeTemplateWithApplication(@PathVariable String templateId,
|
||||
@PathVariable String applicationId,
|
||||
@PathVariable String organizationId,
|
||||
@RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName,
|
||||
@RequestBody(required = false) List<String> pagesToImport) {
|
||||
return applicationTemplateService.mergeTemplateWithApplication(templateId, applicationId, organizationId, branchName, pagesToImport)
|
||||
.map(importedApp -> new ResponseDTO<>(HttpStatus.OK.value(), importedApp, null));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,5 +13,6 @@ public interface ApplicationTemplateServiceCE {
|
|||
Flux<ApplicationTemplate> getRecentlyUsedTemplates();
|
||||
Mono<ApplicationTemplate> getTemplateDetails(String templateId);
|
||||
Mono<Application> importApplicationFromTemplate(String templateId, String organizationId);
|
||||
Mono<Application> mergeTemplateWithApplication(String templateId, String applicationId, String organizationId, String branchName, List<String> pagesToImport);
|
||||
Mono<ApplicationTemplate> getFilters();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import java.util.Comparator;
|
|||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class ApplicationTemplateServiceCEImpl implements ApplicationTemplateServiceCE {
|
||||
|
|
@ -205,4 +206,13 @@ public class ApplicationTemplateServiceCEImpl implements ApplicationTemplateServ
|
|||
super.setEncodingMode(EncodingMode.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Application> mergeTemplateWithApplication(String templateId, String applicationId, String organizationId, String branchName, List<String> pagesToImport) {
|
||||
return getApplicationJsonFromTemplate(templateId).flatMap(applicationJson ->
|
||||
importExportApplicationService.mergeApplicationJsonWithApplication(
|
||||
organizationId, applicationId, null, applicationJson, pagesToImport
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.appsmith.server.services.ce;
|
|||
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationJson;
|
||||
import com.appsmith.server.domains.ApplicationMode;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.services.CrudService;
|
||||
|
|
@ -38,4 +39,5 @@ public interface ThemeServiceCE extends CrudService<Theme, String> {
|
|||
Mono<Theme> updateName(String id, Theme theme);
|
||||
Mono<Theme> getOrSaveTheme(Theme theme, Application destApplication);
|
||||
Mono<Application> archiveApplicationThemes(Application application);
|
||||
Mono<Application> importThemesToApplication(Application destinationApp, ApplicationJson sourceJson);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package com.appsmith.server.services.ce;
|
||||
|
||||
import com.appsmith.external.constants.AnalyticsEvents;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.acl.PolicyGenerator;
|
||||
import com.appsmith.external.constants.AnalyticsEvents;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationJson;
|
||||
import com.appsmith.server.domains.ApplicationMode;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
|
|
@ -391,12 +392,18 @@ public class ThemeServiceCEImpl extends BaseService<ThemeRepositoryCE, Theme, St
|
|||
return repository.getSystemThemeByName(theme.getName())
|
||||
.switchIfEmpty(repository.getSystemThemeByName(Theme.DEFAULT_THEME_NAME));
|
||||
} else {
|
||||
theme.setApplicationId(null);
|
||||
theme.setOrganizationId(null);
|
||||
theme.setPolicies(policyGenerator.getAllChildPolicies(
|
||||
// create a new theme
|
||||
Theme newTheme = new Theme();
|
||||
newTheme.setPolicies(policyGenerator.getAllChildPolicies(
|
||||
destApplication.getPolicies(), Application.class, Theme.class
|
||||
));
|
||||
return repository.save(theme);
|
||||
newTheme.setStylesheet(theme.getStylesheet());
|
||||
newTheme.setProperties(theme.getProperties());
|
||||
newTheme.setConfig(theme.getConfig());
|
||||
newTheme.setName(theme.getName());
|
||||
newTheme.setDisplayName(theme.getDisplayName());
|
||||
newTheme.setSystemTheme(false);
|
||||
return repository.save(newTheme);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -412,4 +419,62 @@ public class ThemeServiceCEImpl extends BaseService<ThemeRepositoryCE, Theme, St
|
|||
.then(repository.archiveDraftThemesById(application.getEditModeThemeId(), application.getPublishedModeThemeId()))
|
||||
.thenReturn(application);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method imports a theme from a JSON file to an application. The destination application can already have
|
||||
* a theme set or not. If no theme is set, it means the application is being created from a JSON import, git import.
|
||||
* In that case, we'll import the edit mode theme and published mode theme from the JSON file and update the application.
|
||||
* If the destination application already has a theme, it means we're doing any of these Git operations -
|
||||
* pull, merge, discard. In this case, we'll decide based on this decision tree:
|
||||
* - If current theme is a customized one and source theme is also customized, replace the current theme properties with source theme properties
|
||||
* - If current theme is a customized one and source theme is system theme, set the current theme to system and delete the old one
|
||||
* - If current theme is system theme, update the current theme as per source theme
|
||||
* @param destinationApp Application object
|
||||
* @param sourceJson ApplicationJSON from file or Git
|
||||
* @return Updated application that has editModeThemeId and publishedModeThemeId set
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Mono<Application> importThemesToApplication(Application destinationApp, ApplicationJson sourceJson) {
|
||||
Mono<Theme> editModeTheme = updateExistingAppThemeFromJSON(
|
||||
destinationApp, destinationApp.getEditModeThemeId(), sourceJson.getEditModeTheme()
|
||||
);
|
||||
|
||||
Mono<Theme> publishedModeTheme = updateExistingAppThemeFromJSON(
|
||||
destinationApp, destinationApp.getPublishedModeThemeId(), sourceJson.getPublishedTheme()
|
||||
);
|
||||
|
||||
return Mono.zip(editModeTheme, publishedModeTheme).flatMap(importedThemesTuple -> {
|
||||
String editModeThemeId = importedThemesTuple.getT1().getId();
|
||||
String publishedModeThemeId = importedThemesTuple.getT2().getId();
|
||||
|
||||
destinationApp.setEditModeThemeId(editModeThemeId);
|
||||
destinationApp.setPublishedModeThemeId(publishedModeThemeId);
|
||||
// this will update the theme id in DB
|
||||
// also returning the updated application object so that theme id are available to the next pipeline
|
||||
return applicationService.setAppTheme(
|
||||
destinationApp.getId(), editModeThemeId, publishedModeThemeId, MANAGE_APPLICATIONS
|
||||
).thenReturn(destinationApp);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Theme> updateExistingAppThemeFromJSON(Application destinationApp, String existingThemeId, Theme themeFromJson) {
|
||||
if(!StringUtils.hasLength(existingThemeId)) {
|
||||
return getOrSaveTheme(themeFromJson, destinationApp);
|
||||
}
|
||||
return repository.findById(existingThemeId, READ_THEMES).flatMap(existingTheme -> {
|
||||
if(existingTheme.isSystemTheme()) {
|
||||
return getOrSaveTheme(themeFromJson, destinationApp);
|
||||
} else {
|
||||
if(themeFromJson.isSystemTheme()) {
|
||||
return getOrSaveTheme(themeFromJson, destinationApp).flatMap(importedTheme -> {
|
||||
// need to delete the old existingTheme
|
||||
return repository.archiveById(existingThemeId).thenReturn(importedTheme);
|
||||
});
|
||||
} else {
|
||||
return repository.updateById(existingThemeId, themeFromJson, MANAGE_THEMES);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package com.appsmith.server.solutions;
|
||||
|
||||
import com.appsmith.server.helpers.PolicyUtils;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationJson;
|
||||
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;
|
||||
|
|
@ -19,7 +23,11 @@ import com.appsmith.server.services.SessionUserService;
|
|||
import com.appsmith.server.services.ThemeService;
|
||||
import com.appsmith.server.solutions.ce.ImportExportApplicationServiceCEImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ public interface ImportExportApplicationServiceCE {
|
|||
*/
|
||||
Mono<ApplicationImportDTO> extractFileAndSaveApplication(String orgId, Part filePart);
|
||||
|
||||
Mono<Application> mergeApplicationJsonWithApplication(String organizationId, String applicationId, String branchName, ApplicationJson applicationJson, List<String> pagesToImport);
|
||||
|
||||
/**
|
||||
* This function will save the application to workspace from the application resource
|
||||
*
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import com.appsmith.server.exceptions.AppsmithError;
|
|||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.DefaultResourcesUtils;
|
||||
import com.appsmith.server.helpers.PolicyUtils;
|
||||
import com.appsmith.server.helpers.TextUtils;
|
||||
import com.appsmith.server.migrations.ApplicationVersion;
|
||||
import com.appsmith.server.migrations.JsonSchemaMigration;
|
||||
import com.appsmith.server.migrations.JsonSchemaVersions;
|
||||
|
|
@ -631,19 +632,48 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
return importApplicationInWorkspace(workspaceId, importedDoc, null, null);
|
||||
}
|
||||
|
||||
public Mono<Application> importApplicationInWorkspace(String workspaceId,
|
||||
ApplicationJson applicationJson,
|
||||
String applicationId,
|
||||
String branchName) {
|
||||
return importApplicationInWorkspace(workspaceId, applicationJson, applicationId, branchName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* validates whether a ApplicationJSON contains the required fields or not.
|
||||
* @param importedDoc ApplicationJSON object that needs to be validated
|
||||
* @return Name of the field that have error. Empty string otherwise
|
||||
*/
|
||||
private String validateApplicationJson(ApplicationJson importedDoc) {
|
||||
String errorField = "";
|
||||
if (CollectionUtils.isEmpty(importedDoc.getPageList())) {
|
||||
errorField = FieldName.PAGES;
|
||||
} else if (importedDoc.getExportedApplication() == null) {
|
||||
errorField = FieldName.APPLICATION;
|
||||
} else if (importedDoc.getActionList() == null) {
|
||||
errorField = FieldName.ACTIONS;
|
||||
} else if (importedDoc.getDatasourceList() == null) {
|
||||
errorField = FieldName.DATASOURCE;
|
||||
}
|
||||
|
||||
return errorField;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will take the application reference object to hydrate the application in mongoDB
|
||||
*
|
||||
* @param workspaceId workspace to which application is going to be stored
|
||||
* @param applicationJson application resource which contains necessary information to import the application
|
||||
* @param applicationId application which needs to be saved with the updated resources
|
||||
* @param branchName name of the branch of application with applicationId
|
||||
* @param appendToApp whether applicationJson will be appended to the existing app or not
|
||||
* @return Updated application
|
||||
*/
|
||||
public Mono<Application> importApplicationInWorkspace(String workspaceId,
|
||||
ApplicationJson applicationJson,
|
||||
String applicationId,
|
||||
String branchName) {
|
||||
|
||||
private Mono<Application> importApplicationInWorkspace(String workspaceId,
|
||||
ApplicationJson applicationJson,
|
||||
String applicationId,
|
||||
String branchName,
|
||||
boolean appendToApp) {
|
||||
/*
|
||||
1. Migrate resource to latest schema
|
||||
2. Fetch workspace by id
|
||||
|
|
@ -653,10 +683,18 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
6. Extract and save pages in the application
|
||||
7. Extract and save actions in the application
|
||||
*/
|
||||
// Start the stopwatch to log the execution time
|
||||
Stopwatch processStopwatch = new Stopwatch("Import application");
|
||||
|
||||
|
||||
ApplicationJson importedDoc = JsonSchemaMigration.migrateApplicationToLatestSchema(applicationJson);
|
||||
|
||||
// check for validation error and raise exception if error found
|
||||
String errorField = validateApplicationJson(importedDoc);
|
||||
if (!errorField.isEmpty()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, errorField, INVALID_JSON_FILE));
|
||||
}
|
||||
|
||||
Map<String, String> pluginMap = new HashMap<>();
|
||||
Map<String, String> datasourceMap = new HashMap<>();
|
||||
Map<String, NewPage> pageNameMap = new HashMap<>();
|
||||
|
|
@ -681,20 +719,6 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
.findAllByOrganizationId(workspaceId, MANAGE_DATASOURCES)
|
||||
.cache();
|
||||
|
||||
String errorField = "";
|
||||
if (CollectionUtils.isEmpty(importedNewPageList)) {
|
||||
errorField = FieldName.PAGES;
|
||||
} else if (importedApplication == null) {
|
||||
errorField = FieldName.APPLICATION;
|
||||
} else if (importedNewActionList == null) {
|
||||
errorField = FieldName.ACTIONS;
|
||||
} else if (importedDatasourceList == null) {
|
||||
errorField = FieldName.DATASOURCE;
|
||||
}
|
||||
|
||||
if (!errorField.isEmpty()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, errorField, INVALID_JSON_FILE));
|
||||
}
|
||||
assert importedApplication != null: "Received invalid application object!";
|
||||
if(importedApplication.getApplicationVersion() == null) {
|
||||
importedApplication.setApplicationVersion(ApplicationVersion.EARLIEST_VERSION);
|
||||
|
|
@ -722,7 +746,6 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
return Mono.just(new ArrayList<Datasource>());
|
||||
})
|
||||
.flatMapMany(existingDatasources -> {
|
||||
|
||||
if (CollectionUtils.isEmpty(importedDatasourceList)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
|
@ -810,6 +833,9 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
applicationId))
|
||||
)
|
||||
.flatMap(existingApplication -> {
|
||||
if(StringUtils.isEmpty(branchName)) {
|
||||
return Mono.just(existingApplication);
|
||||
}
|
||||
importedApplication.setId(existingApplication.getId());
|
||||
// For the existing application we don't need to default value of the flag
|
||||
// The isPublic flag has a default value as false and this would be confusing to user
|
||||
|
|
@ -845,7 +871,7 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
return applicationPageService.createOrUpdateSuffixedApplication(application, application.getName(), 0);
|
||||
})
|
||||
)
|
||||
.flatMap(savedApp -> importThemes(savedApp, importedDoc))
|
||||
.flatMap(savedApp -> importThemes(savedApp, importedDoc, appendToApp))
|
||||
.flatMap(savedApp -> {
|
||||
importedApplication.setId(savedApp.getId());
|
||||
if (savedApp.getGitApplicationMetadata() != null) {
|
||||
|
|
@ -855,20 +881,49 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
// Import and save pages, also update the pages related fields in saved application
|
||||
assert importedNewPageList != null: "Unable to find pages in the imported application";
|
||||
|
||||
if(appendToApp) {
|
||||
// add existing pages to importedApplication so that they are not lost
|
||||
// when we update application from importedApplication
|
||||
importedApplication.setPages(savedApp.getPages());
|
||||
}
|
||||
|
||||
// For git-sync this will not be empty
|
||||
Mono<List<NewPage>> existingPagesMono = newPageService
|
||||
.findNewPagesByApplicationId(importedApplication.getId(), MANAGE_PAGES)
|
||||
.collectList()
|
||||
.cache();
|
||||
|
||||
return importAndSavePages(
|
||||
Flux<NewPage> importNewPageFlux = importAndSavePages(
|
||||
importedNewPageList,
|
||||
importedApplication,
|
||||
savedApp,
|
||||
importedDoc.getPublishedLayoutmongoEscapedWidgets(),
|
||||
importedDoc.getUnpublishedLayoutmongoEscapedWidgets(),
|
||||
branchName,
|
||||
existingPagesMono
|
||||
)
|
||||
);
|
||||
Flux<NewPage> importedNewPagesMono;
|
||||
|
||||
if(appendToApp) {
|
||||
// we need to rename page if there is a conflict
|
||||
// also need to remap the renamed page
|
||||
importedNewPagesMono = updateNewPagesBeforeMerge(existingPagesMono, importedNewPageList)
|
||||
.flatMapMany(newToOldNameMap->
|
||||
importNewPageFlux.map(newPage -> {
|
||||
// we need to map the newly created page with old name
|
||||
// because other related resources e.g. actions will refer the page with old name
|
||||
String newPageName = newPage.getUnpublishedPage().getName();
|
||||
String oldPageName = newToOldNameMap.get(newPageName);
|
||||
if(!newPageName.equals(oldPageName)) {
|
||||
renamePageInActions(importedNewActionList, oldPageName, newPageName);
|
||||
renamePageInActionCollections(importedActionCollectionList, oldPageName, newPageName);
|
||||
}
|
||||
return newPage;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
importedNewPagesMono = importNewPageFlux;
|
||||
}
|
||||
importedNewPagesMono = importedNewPagesMono
|
||||
.map(newPage -> {
|
||||
// Save the map of pageName and NewPage
|
||||
if (newPage.getUnpublishedPage() != null && newPage.getUnpublishedPage().getName() != null) {
|
||||
|
|
@ -878,75 +933,15 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
pageNameMap.put(newPage.getPublishedPage().getName(), newPage);
|
||||
}
|
||||
return newPage;
|
||||
})
|
||||
});
|
||||
|
||||
return importedNewPagesMono
|
||||
.collectList()
|
||||
.map(newPageList -> {
|
||||
Map<PublishType, List<ApplicationPage>> applicationPages = Map.of(
|
||||
PublishType.UNPUBLISHED, new ArrayList<>(),
|
||||
PublishType.PUBLISHED, new ArrayList<>()
|
||||
);
|
||||
// Reorder the pages based on edit mode page sequence
|
||||
List<String> pageOrderList;
|
||||
if(!CollectionUtils.isEmpty(applicationJson.getPageOrder())) {
|
||||
pageOrderList = applicationJson.getPageOrder();
|
||||
} else {
|
||||
pageOrderList = pageNameMap.values()
|
||||
.stream()
|
||||
.map(newPage -> newPage.getUnpublishedPage().getName())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
for(String pageName : pageOrderList) {
|
||||
NewPage newPage = pageNameMap.get(pageName);
|
||||
ApplicationPage unpublishedAppPage = new ApplicationPage();
|
||||
if (newPage.getUnpublishedPage() != null && newPage.getUnpublishedPage().getName() != null) {
|
||||
unpublishedAppPage.setIsDefault(
|
||||
StringUtils.equals(
|
||||
newPage.getUnpublishedPage().getName(), importedDoc.getUnpublishedDefaultPageName()
|
||||
)
|
||||
);
|
||||
unpublishedAppPage.setId(newPage.getId());
|
||||
if (newPage.getDefaultResources() != null) {
|
||||
unpublishedAppPage.setDefaultPageId(newPage.getDefaultResources().getPageId());
|
||||
}
|
||||
}
|
||||
if (unpublishedAppPage.getId() != null && newPage.getUnpublishedPage().getDeletedAt() == null) {
|
||||
applicationPages.get(PublishType.UNPUBLISHED).add(unpublishedAppPage);
|
||||
}
|
||||
}
|
||||
|
||||
// Reorder the pages based on view mode page sequence
|
||||
List<String> publishedPageOrderList;
|
||||
if(!CollectionUtils.isEmpty(applicationJson.getPublishedPageOrder())) {
|
||||
publishedPageOrderList = applicationJson.getPublishedPageOrder();
|
||||
} else {
|
||||
publishedPageOrderList = pageNameMap.values()
|
||||
.stream()
|
||||
.filter(newPage -> Optional.ofNullable(newPage.getPublishedPage()).isPresent())
|
||||
.map(newPage -> newPage.getPublishedPage().getName()).collect(Collectors.toList());
|
||||
}
|
||||
for(String pageName : publishedPageOrderList) {
|
||||
NewPage newPage = pageNameMap.get(pageName);
|
||||
ApplicationPage publishedAppPage = new ApplicationPage();
|
||||
if (newPage.getPublishedPage() != null && newPage.getPublishedPage().getName() != null) {
|
||||
publishedAppPage.setIsDefault(
|
||||
StringUtils.equals(
|
||||
newPage.getPublishedPage().getName(), importedDoc.getPublishedDefaultPageName()
|
||||
)
|
||||
);
|
||||
publishedAppPage.setId(newPage.getId());
|
||||
if (newPage.getDefaultResources() != null) {
|
||||
publishedAppPage.setDefaultPageId(newPage.getDefaultResources().getPageId());
|
||||
}
|
||||
}
|
||||
|
||||
if (publishedAppPage.getId() != null && newPage.getPublishedPage().getDeletedAt() == null) {
|
||||
applicationPages.get(PublishType.PUBLISHED).add(publishedAppPage);
|
||||
}
|
||||
}
|
||||
return applicationPages;
|
||||
return reorderPages(applicationJson, importedDoc, pageNameMap);
|
||||
})
|
||||
.flatMap(applicationPages -> {
|
||||
if (!StringUtils.isEmpty(applicationId)) {
|
||||
if (!StringUtils.isEmpty(applicationId) && !appendToApp) {
|
||||
Set<String> validPageIds = applicationPages.get(PublishType.UNPUBLISHED).stream()
|
||||
.map(ApplicationPage::getId)
|
||||
.collect(Collectors.toSet());
|
||||
|
|
@ -984,9 +979,19 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
})
|
||||
.flatMap(applicationPageMap -> {
|
||||
// set it based on the order for published and unublished pages
|
||||
importedApplication.setPages(applicationPageMap.get(PublishType.UNPUBLISHED));
|
||||
importedApplication.setPublishedPages(applicationPageMap.get(PublishType.PUBLISHED));
|
||||
|
||||
if(appendToApp) {
|
||||
if(importedApplication.getPages() == null) {
|
||||
importedApplication.setPages(new ArrayList<>());
|
||||
}
|
||||
// new pages should not be a default one, existing default page should not change
|
||||
applicationPageMap.get(PublishType.UNPUBLISHED)
|
||||
.forEach(applicationPage -> applicationPage.setIsDefault(false));
|
||||
// append the new pages so that existing pages not lost when we update later
|
||||
importedApplication.getPages().addAll(applicationPageMap.get(PublishType.UNPUBLISHED));
|
||||
} else {
|
||||
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();
|
||||
|
|
@ -1005,34 +1010,34 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
publishedCollectionIdToActionIdsMap,
|
||||
applicationJson.getInvisibleActionFields()
|
||||
)
|
||||
.map(NewAction::getId)
|
||||
.collectList()
|
||||
.flatMap(savedActionIds -> {
|
||||
// Updating the existing application for git-sync
|
||||
if (!StringUtils.isEmpty(applicationId)) {
|
||||
// Remove unwanted actions
|
||||
Set<String> invalidActionIds = new HashSet<>();
|
||||
for (NewAction action : existingActions) {
|
||||
if (!savedActionIds.contains(action.getId())) {
|
||||
invalidActionIds.add(action.getId());
|
||||
.map(NewAction::getId)
|
||||
.collectList()
|
||||
.flatMap(savedActionIds -> {
|
||||
// Updating the existing application for git-sync
|
||||
if (!StringUtils.isEmpty(applicationId) && !appendToApp) {
|
||||
// Remove unwanted actions
|
||||
Set<String> invalidActionIds = new HashSet<>();
|
||||
for (NewAction action : existingActions) {
|
||||
if (!savedActionIds.contains(action.getId())) {
|
||||
invalidActionIds.add(action.getId());
|
||||
}
|
||||
}
|
||||
return Flux.fromIterable(invalidActionIds)
|
||||
.flatMap(actionId -> newActionService.deleteUnpublishedAction(actionId)
|
||||
// return an empty action so that the filter can remove it from the list
|
||||
.onErrorResume(throwable -> {
|
||||
log.debug("Failed to delete action with id {} during import", actionId);
|
||||
log.error(throwable.getMessage());
|
||||
return Mono.empty();
|
||||
})
|
||||
)
|
||||
.then()
|
||||
.thenReturn(savedActionIds);
|
||||
}
|
||||
}
|
||||
return Flux.fromIterable(invalidActionIds)
|
||||
.flatMap(actionId -> newActionService.deleteUnpublishedAction(actionId)
|
||||
// return an empty action so that the filter can remove it from the list
|
||||
.onErrorResume(throwable -> {
|
||||
log.debug("Failed to delete action with id {} during import", actionId);
|
||||
log.error(throwable.getMessage());
|
||||
return Mono.empty();
|
||||
})
|
||||
)
|
||||
.then()
|
||||
.thenReturn(savedActionIds);
|
||||
}
|
||||
return Mono.just(savedActionIds);
|
||||
})
|
||||
.thenMany(actionCollectionRepository.findByApplicationId(importedApplication.getId()))
|
||||
.collectList()
|
||||
return Mono.just(savedActionIds);
|
||||
})
|
||||
.thenMany(actionCollectionRepository.findByApplicationId(importedApplication.getId()))
|
||||
.collectList()
|
||||
)
|
||||
.flatMap(existingActionCollections -> {
|
||||
if (importedActionCollectionList == null) {
|
||||
|
|
@ -1047,46 +1052,46 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
pageNameMap, pluginMap,
|
||||
unpublishedCollectionIdToActionIdsMap,
|
||||
publishedCollectionIdToActionIdsMap
|
||||
)
|
||||
.flatMap(tuple -> {
|
||||
final String importedActionCollectionId = tuple.getT1();
|
||||
ActionCollection savedActionCollection = tuple.getT2();
|
||||
savedCollectionIds.add(savedActionCollection.getId());
|
||||
return updateActionsWithImportedCollectionIds(
|
||||
importedActionCollectionId,
|
||||
savedActionCollection,
|
||||
unpublishedCollectionIdToActionIdsMap,
|
||||
publishedCollectionIdToActionIdsMap,
|
||||
unpublishedActionIdToCollectionIdMap,
|
||||
publishedActionIdToCollectionIdMap
|
||||
);
|
||||
})
|
||||
.collectList()
|
||||
.flatMap(ignore -> {
|
||||
// Updating the existing application for git-sync
|
||||
if (!StringUtils.isEmpty(applicationId)) {
|
||||
// Remove unwanted actions
|
||||
Set<String> invalidCollectionIds = new HashSet<>();
|
||||
for (ActionCollection collection : existingActionCollections) {
|
||||
if (!savedCollectionIds.contains(collection.getId())) {
|
||||
invalidCollectionIds.add(collection.getId());
|
||||
)
|
||||
.flatMap(tuple -> {
|
||||
final String importedActionCollectionId = tuple.getT1();
|
||||
ActionCollection savedActionCollection = tuple.getT2();
|
||||
savedCollectionIds.add(savedActionCollection.getId());
|
||||
return updateActionsWithImportedCollectionIds(
|
||||
importedActionCollectionId,
|
||||
savedActionCollection,
|
||||
unpublishedCollectionIdToActionIdsMap,
|
||||
publishedCollectionIdToActionIdsMap,
|
||||
unpublishedActionIdToCollectionIdMap,
|
||||
publishedActionIdToCollectionIdMap
|
||||
);
|
||||
})
|
||||
.collectList()
|
||||
.flatMap(ignore -> {
|
||||
// Updating the existing application for git-sync
|
||||
if (!StringUtils.isEmpty(applicationId) && !appendToApp) {
|
||||
// Remove unwanted action collections
|
||||
Set<String> invalidCollectionIds = new HashSet<>();
|
||||
for (ActionCollection collection : existingActionCollections) {
|
||||
if (!savedCollectionIds.contains(collection.getId())) {
|
||||
invalidCollectionIds.add(collection.getId());
|
||||
}
|
||||
}
|
||||
return Flux.fromIterable(invalidCollectionIds)
|
||||
.flatMap(collectionId -> actionCollectionService.deleteUnpublishedActionCollection(collectionId)
|
||||
// return an empty collection so that the filter can remove it from the list
|
||||
.onErrorResume(throwable -> {
|
||||
log.debug("Failed to delete collection with id {} during import", collectionId);
|
||||
log.error(throwable.getMessage());
|
||||
return Mono.empty();
|
||||
})
|
||||
)
|
||||
.then()
|
||||
.thenReturn(savedCollectionIds);
|
||||
}
|
||||
return Flux.fromIterable(invalidCollectionIds)
|
||||
.flatMap(collectionId -> actionCollectionService.deleteUnpublishedActionCollection(collectionId)
|
||||
// return an empty collection so that the filter can remove it from the list
|
||||
.onErrorResume(throwable -> {
|
||||
log.debug("Failed to delete collection with id {} during import", collectionId);
|
||||
log.error(throwable.getMessage());
|
||||
return Mono.empty();
|
||||
})
|
||||
)
|
||||
.then()
|
||||
.thenReturn(savedCollectionIds);
|
||||
}
|
||||
return Mono.just(savedCollectionIds);
|
||||
})
|
||||
.thenReturn(true);
|
||||
return Mono.just(savedCollectionIds);
|
||||
})
|
||||
.thenReturn(true);
|
||||
})
|
||||
.flatMap(ignored -> {
|
||||
// Don't update gitAuth as we are using @Encrypted for private key
|
||||
|
|
@ -1133,6 +1138,88 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
);
|
||||
}
|
||||
|
||||
private void renamePageInActions(List<NewAction> newActionList, String oldPageName, String newPageName) {
|
||||
for(NewAction newAction : newActionList) {
|
||||
if(newAction.getUnpublishedAction().getPageId().equals(oldPageName)) {
|
||||
newAction.getUnpublishedAction().setPageId(newPageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renamePageInActionCollections(List<ActionCollection> actionCollectionList, String oldPageName, String newPageName) {
|
||||
for(ActionCollection actionCollection : actionCollectionList) {
|
||||
if(actionCollection.getUnpublishedCollection().getPageId().equals(oldPageName)) {
|
||||
actionCollection.getUnpublishedCollection().setPageId(newPageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<PublishType, List<ApplicationPage>> reorderPages(ApplicationJson applicationJson, ApplicationJson importedDoc, Map<String, NewPage> pageNameMap) {
|
||||
Map<PublishType, List<ApplicationPage>> applicationPages = Map.of(
|
||||
PublishType.UNPUBLISHED, new ArrayList<>(),
|
||||
PublishType.PUBLISHED, new ArrayList<>()
|
||||
);
|
||||
// Reorder the pages based on edit mode page sequence
|
||||
List<String> pageOrderList;
|
||||
if(!CollectionUtils.isEmpty(applicationJson.getPageOrder())) {
|
||||
pageOrderList = applicationJson.getPageOrder();
|
||||
} else {
|
||||
pageOrderList = pageNameMap.values()
|
||||
.stream()
|
||||
.map(newPage -> newPage.getUnpublishedPage().getName())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
for(String pageName : pageOrderList) {
|
||||
NewPage newPage = pageNameMap.get(pageName);
|
||||
ApplicationPage unpublishedAppPage = new ApplicationPage();
|
||||
if (newPage.getUnpublishedPage() != null && newPage.getUnpublishedPage().getName() != null) {
|
||||
unpublishedAppPage.setIsDefault(
|
||||
StringUtils.equals(
|
||||
newPage.getUnpublishedPage().getName(), importedDoc.getUnpublishedDefaultPageName()
|
||||
)
|
||||
);
|
||||
unpublishedAppPage.setId(newPage.getId());
|
||||
if (newPage.getDefaultResources() != null) {
|
||||
unpublishedAppPage.setDefaultPageId(newPage.getDefaultResources().getPageId());
|
||||
}
|
||||
}
|
||||
if (unpublishedAppPage.getId() != null && newPage.getUnpublishedPage().getDeletedAt() == null) {
|
||||
applicationPages.get(PublishType.UNPUBLISHED).add(unpublishedAppPage);
|
||||
}
|
||||
}
|
||||
|
||||
// Reorder the pages based on view mode page sequence
|
||||
List<String> publishedPageOrderList;
|
||||
if(!CollectionUtils.isEmpty(applicationJson.getPublishedPageOrder())) {
|
||||
publishedPageOrderList = applicationJson.getPublishedPageOrder();
|
||||
} else {
|
||||
publishedPageOrderList = pageNameMap.values()
|
||||
.stream()
|
||||
.filter(newPage -> Optional.ofNullable(newPage.getPublishedPage()).isPresent())
|
||||
.map(newPage -> newPage.getPublishedPage().getName()).collect(Collectors.toList());
|
||||
}
|
||||
for(String pageName : publishedPageOrderList) {
|
||||
NewPage newPage = pageNameMap.get(pageName);
|
||||
ApplicationPage publishedAppPage = new ApplicationPage();
|
||||
if (newPage.getPublishedPage() != null && newPage.getPublishedPage().getName() != null) {
|
||||
publishedAppPage.setIsDefault(
|
||||
StringUtils.equals(
|
||||
newPage.getPublishedPage().getName(), importedDoc.getPublishedDefaultPageName()
|
||||
)
|
||||
);
|
||||
publishedAppPage.setId(newPage.getId());
|
||||
if (newPage.getDefaultResources() != null) {
|
||||
publishedAppPage.setDefaultPageId(newPage.getDefaultResources().getPageId());
|
||||
}
|
||||
}
|
||||
|
||||
if (publishedAppPage.getId() != null && newPage.getPublishedPage().getDeletedAt() == null) {
|
||||
applicationPages.get(PublishType.PUBLISHED).add(publishedAppPage);
|
||||
}
|
||||
}
|
||||
return applicationPages;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will respond with unique suffixed number for the entity to avoid duplicate names
|
||||
*
|
||||
|
|
@ -1922,22 +2009,11 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
return srcTheme;
|
||||
}
|
||||
|
||||
private Mono<Application> importThemes(Application application, ApplicationJson importedApplicationJson) {
|
||||
Mono<Theme> importedEditModeTheme = themeService.getOrSaveTheme(importedApplicationJson.getEditModeTheme(), application);
|
||||
Mono<Theme> importedPublishedModeTheme = themeService.getOrSaveTheme(importedApplicationJson.getPublishedTheme(), application);
|
||||
|
||||
return Mono.zip(importedEditModeTheme, importedPublishedModeTheme).flatMap(importedThemesTuple -> {
|
||||
String editModeThemeId = importedThemesTuple.getT1().getId();
|
||||
String publishedModeThemeId = importedThemesTuple.getT2().getId();
|
||||
|
||||
application.setEditModeThemeId(editModeThemeId);
|
||||
application.setPublishedModeThemeId(publishedModeThemeId);
|
||||
// this will update the theme id in DB
|
||||
// also returning the updated application object so that theme id are available to the next pipeline
|
||||
return applicationService.setAppTheme(
|
||||
application.getId(), editModeThemeId, publishedModeThemeId, MANAGE_APPLICATIONS
|
||||
).thenReturn(application);
|
||||
});
|
||||
private Mono<Application> importThemes(Application application, ApplicationJson importedApplicationJson, boolean appendToApp) {
|
||||
if(appendToApp) { // appending to existing app, theme should not change
|
||||
return Mono.just(application);
|
||||
}
|
||||
return themeService.importThemesToApplication(application, importedApplicationJson);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2011,4 +2087,93 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
application.setServerSchemaVersion(null);
|
||||
application.setIsManualUpdate(false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param applicationId default ID of the application where this ApplicationJSON is going to get merged with
|
||||
* @param branchName name of the branch of the application where this ApplicationJSON is going to get merged with
|
||||
* @param applicationJson ApplicationJSON of the application that will be merged to
|
||||
* @param pagesToImport Name of the pages that should be merged from the ApplicationJSON.
|
||||
* If null or empty, all pages will be merged.
|
||||
* @return Merged Application
|
||||
*/
|
||||
@Override
|
||||
public Mono<Application> mergeApplicationJsonWithApplication(String workspaceId, String applicationId, String branchName, ApplicationJson applicationJson, List<String> pagesToImport) {
|
||||
// Update the application JSON to prepare it for merging inside an existing application
|
||||
if(applicationJson.getExportedApplication() != null) {
|
||||
// setting some properties to null so that target application is not updated by these properties
|
||||
applicationJson.getExportedApplication().setName(null);
|
||||
applicationJson.getExportedApplication().setSlug(null);
|
||||
applicationJson.getExportedApplication().setApplicationVersion(null);
|
||||
applicationJson.getExportedApplication().setForkingEnabled(null);
|
||||
applicationJson.getExportedApplication().setClonedFromApplicationId(null);
|
||||
}
|
||||
|
||||
// need to remove git sync id. Also filter pages if pageToImport is not empty
|
||||
if(applicationJson.getPageList() != null) {
|
||||
List<NewPage> importedNewPageList = applicationJson.getPageList().stream()
|
||||
.filter(newPage -> newPage.getUnpublishedPage() != null &&
|
||||
(CollectionUtils.isEmpty(pagesToImport) ||
|
||||
pagesToImport.contains(newPage.getUnpublishedPage().getName()))
|
||||
).collect(Collectors.toList());
|
||||
applicationJson.setPageList(importedNewPageList);
|
||||
}
|
||||
if(applicationJson.getActionList() != null) {
|
||||
List<NewAction> importedNewActionList = applicationJson.getActionList().stream()
|
||||
.filter(newAction ->
|
||||
newAction.getUnpublishedAction() != null &&
|
||||
(CollectionUtils.isEmpty(pagesToImport) ||
|
||||
pagesToImport.contains(newAction.getUnpublishedAction().getPageId()))
|
||||
).peek(newAction -> newAction.setGitSyncId(null)) // setting this null so that this action can be imported again
|
||||
.collect(Collectors.toList());
|
||||
applicationJson.setActionList(importedNewActionList);
|
||||
}
|
||||
if(applicationJson.getActionCollectionList() != null) {
|
||||
List<ActionCollection> importedActionCollectionList = applicationJson.getActionCollectionList().stream()
|
||||
.filter(actionCollection ->
|
||||
(CollectionUtils.isEmpty(pagesToImport) ||
|
||||
pagesToImport.contains(actionCollection.getUnpublishedCollection().getPageId()))
|
||||
).peek(actionCollection -> actionCollection.setGitSyncId(null)) // setting this null so that this action collection can be imported again
|
||||
.collect(Collectors.toList());
|
||||
applicationJson.setActionCollectionList(importedActionCollectionList);
|
||||
}
|
||||
|
||||
return importApplicationInWorkspace(workspaceId, applicationJson, applicationId, branchName, true);
|
||||
}
|
||||
|
||||
private Mono<Map<String, String>> updateNewPagesBeforeMerge(Mono<List<NewPage>> existingPagesMono, List<NewPage> newPagesList) {
|
||||
return existingPagesMono.map(newPages -> {
|
||||
Map<String, String> newToOldToPageNameMap = new HashMap<>(); // maps new names with old names
|
||||
|
||||
// get a list of unpublished page names that already exists
|
||||
List<String> unpublishedPageNames = newPages.stream()
|
||||
.filter(newPage -> newPage.getUnpublishedPage() != null)
|
||||
.map(newPage -> newPage.getUnpublishedPage().getName())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// modify each new page
|
||||
for (NewPage newPage : newPagesList) {
|
||||
newPage.setPublishedPage(null); // we'll not merge published pages so removing this
|
||||
/* Need to remove gitSyncId from imported new pages.
|
||||
* Same template or page can be merged with same application multiple times.
|
||||
* As gitSyncId will be same for each import, new page will not be created
|
||||
*/
|
||||
newPage.setGitSyncId(null);
|
||||
|
||||
// let's check if page name conflicts, rename in that case
|
||||
String oldPageName = newPage.getUnpublishedPage().getName(),
|
||||
newPageName = newPage.getUnpublishedPage().getName();
|
||||
|
||||
int i = 1;
|
||||
while (unpublishedPageNames.contains(newPageName)) {
|
||||
i++;
|
||||
newPageName = oldPageName + i;
|
||||
}
|
||||
newPage.getUnpublishedPage().setName(newPageName); // set new name. may be same as before or not
|
||||
newPage.getUnpublishedPage().setSlug(TextUtils.makeSlug(newPageName)); // set the slug also
|
||||
newToOldToPageNameMap.put(newPageName, oldPageName); // map: new name -> old name
|
||||
}
|
||||
return newToOldToPageNameMap;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.appsmith.external.models.Policy;
|
|||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationJson;
|
||||
import com.appsmith.server.domains.ApplicationMode;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.dtos.ApplicationAccessDTO;
|
||||
|
|
@ -25,6 +26,7 @@ import reactor.util.function.Tuples;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -745,4 +747,37 @@ public class ThemeServiceTest {
|
|||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@WithUserDetails("api_user")
|
||||
@Test
|
||||
public void importThemesToApplication_WhenBothImportedThemesAreCustom_NewThemesCreated() {
|
||||
Application application = createApplication("api_user", Set.of(MANAGE_APPLICATIONS));
|
||||
application.setOrganizationId("test-org");
|
||||
|
||||
// create a application json with a custom theme set as both edit mode and published mode
|
||||
ApplicationJson applicationJson = new ApplicationJson();
|
||||
Theme customTheme = new Theme();
|
||||
customTheme.setName("Custom theme name");
|
||||
customTheme.setDisplayName("Custom theme display name");
|
||||
applicationJson.setEditModeTheme(customTheme);
|
||||
applicationJson.setPublishedTheme(customTheme);
|
||||
|
||||
Mono<Application> applicationMono = themeService.getDefaultThemeId()
|
||||
.flatMap(defaultThemeId -> {
|
||||
application.setEditModeThemeId(defaultThemeId);
|
||||
application.setPublishedModeThemeId(defaultThemeId);
|
||||
return applicationRepository.save(application);
|
||||
})
|
||||
.flatMap(savedApplication ->
|
||||
themeService.importThemesToApplication(savedApplication, applicationJson)
|
||||
.thenReturn(Objects.requireNonNull(savedApplication.getId()))
|
||||
)
|
||||
.flatMap(applicationId ->
|
||||
applicationRepository.findById(applicationId, MANAGE_APPLICATIONS)
|
||||
);
|
||||
|
||||
StepVerifier.create(applicationMono).assertNext(app -> {
|
||||
assertThat(app.getEditModeThemeId().equals(app.getPublishedModeThemeId())).isFalse();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -13,28 +13,31 @@ 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.ApplicationMode;
|
||||
import com.appsmith.server.domains.ApplicationPage;
|
||||
import com.appsmith.server.domains.GitApplicationMetadata;
|
||||
import com.appsmith.server.domains.Layout;
|
||||
import com.appsmith.server.domains.NewAction;
|
||||
import com.appsmith.server.domains.NewPage;
|
||||
import com.appsmith.server.domains.Workspace;
|
||||
import com.appsmith.server.domains.Plugin;
|
||||
import com.appsmith.server.domains.PluginType;
|
||||
import com.appsmith.server.domains.Theme;
|
||||
import com.appsmith.server.domains.Workspace;
|
||||
import com.appsmith.server.dtos.ActionCollectionDTO;
|
||||
import com.appsmith.server.dtos.ActionDTO;
|
||||
import com.appsmith.server.dtos.ApplicationAccessDTO;
|
||||
import com.appsmith.server.dtos.ApplicationImportDTO;
|
||||
import com.appsmith.server.dtos.ApplicationPagesDTO;
|
||||
import com.appsmith.server.dtos.PageDTO;
|
||||
import com.appsmith.server.dtos.PageNameIdDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.MockPluginExecutor;
|
||||
import com.appsmith.server.helpers.PluginExecutorHelper;
|
||||
import com.appsmith.server.migrations.ApplicationVersion;
|
||||
import com.appsmith.server.migrations.JsonSchemaMigration;
|
||||
import com.appsmith.server.migrations.JsonSchemaVersions;
|
||||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
import com.appsmith.server.repositories.NewPageRepository;
|
||||
import com.appsmith.server.repositories.PluginRepository;
|
||||
import com.appsmith.server.repositories.ThemeRepository;
|
||||
import com.appsmith.server.services.ActionCollectionService;
|
||||
|
|
@ -46,9 +49,6 @@ import com.appsmith.server.services.LayoutCollectionService;
|
|||
import com.appsmith.server.services.NewActionService;
|
||||
import com.appsmith.server.services.NewPageService;
|
||||
import com.appsmith.server.services.WorkspaceService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.ThemeService;
|
||||
import com.appsmith.server.services.UserService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
|
@ -82,6 +82,7 @@ import org.springframework.util.LinkedMultiValueMap;
|
|||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.util.function.Tuple3;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.Duration;
|
||||
|
|
@ -120,9 +121,6 @@ public class ImportExportApplicationServiceTests {
|
|||
@Autowired
|
||||
ApplicationPageService applicationPageService;
|
||||
|
||||
@Autowired
|
||||
UserService userService;
|
||||
|
||||
@Autowired
|
||||
PluginRepository pluginRepository;
|
||||
|
||||
|
|
@ -141,15 +139,9 @@ public class ImportExportApplicationServiceTests {
|
|||
@Autowired
|
||||
WorkspaceService workspaceService;
|
||||
|
||||
@Autowired
|
||||
SessionUserService sessionUserService;
|
||||
|
||||
@Autowired
|
||||
LayoutActionService layoutActionService;
|
||||
|
||||
@Autowired
|
||||
NewPageRepository newPageRepository;
|
||||
|
||||
@Autowired
|
||||
LayoutCollectionService layoutCollectionService;
|
||||
|
||||
|
|
@ -162,9 +154,6 @@ public class ImportExportApplicationServiceTests {
|
|||
@Autowired
|
||||
ThemeRepository themeRepository;
|
||||
|
||||
@Autowired
|
||||
ThemeService themeService;
|
||||
|
||||
@Autowired
|
||||
ApplicationService applicationService;
|
||||
|
||||
|
|
@ -2334,7 +2323,7 @@ public class ImportExportApplicationServiceTests {
|
|||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void exportAndImportApplication_withMultiplePagesOrderSameInDeployAndEditMode_PagesOrderIsMaintainedInEditAndViewMode() {
|
||||
|
|
@ -2525,6 +2514,155 @@ public class ImportExportApplicationServiceTests {
|
|||
|
||||
}
|
||||
|
||||
private ApplicationJson createApplicationJSON(List<String> pageNames) {
|
||||
ApplicationJson applicationJson = new ApplicationJson();
|
||||
|
||||
// set the application data
|
||||
Application application = new Application();
|
||||
application.setName("Template Application");
|
||||
application.setSlug("template-application");
|
||||
application.setForkingEnabled(true);
|
||||
application.setIsPublic(true);
|
||||
application.setApplicationVersion(ApplicationVersion.LATEST_VERSION);
|
||||
applicationJson.setExportedApplication(application);
|
||||
|
||||
Datasource sampleDatasource = new Datasource();
|
||||
sampleDatasource.setName("SampleDS");
|
||||
sampleDatasource.setPluginId("restapi-plugin");
|
||||
|
||||
applicationJson.setDatasourceList(List.of(sampleDatasource));
|
||||
|
||||
// add pages and actions
|
||||
List<NewPage> newPageList = new ArrayList<>(pageNames.size());
|
||||
List<NewAction> actionList = new ArrayList<>();
|
||||
List<ActionCollection> actionCollectionList = new ArrayList<>();
|
||||
|
||||
for(String pageName : pageNames) {
|
||||
NewPage newPage = new NewPage();
|
||||
newPage.setUnpublishedPage(new PageDTO());
|
||||
newPage.getUnpublishedPage().setName(pageName);
|
||||
newPage.getUnpublishedPage().setLayouts(List.of());
|
||||
newPageList.add(newPage);
|
||||
|
||||
NewAction action = new NewAction();
|
||||
action.setId(pageName + "_SampleQuery");
|
||||
action.setPluginType(PluginType.API);
|
||||
action.setPluginId("restapi-plugin");
|
||||
action.setUnpublishedAction(new ActionDTO());
|
||||
action.getUnpublishedAction().setName("SampleQuery");
|
||||
action.getUnpublishedAction().setPageId(pageName);
|
||||
action.getUnpublishedAction().setDatasource(new Datasource());
|
||||
action.getUnpublishedAction().getDatasource().setId("SampleDS");
|
||||
action.getUnpublishedAction().getDatasource().setPluginId("restapi-plugin");
|
||||
actionList.add(action);
|
||||
|
||||
ActionCollection actionCollection = new ActionCollection();
|
||||
actionCollection.setId(pageName + "_SampleJS");
|
||||
actionCollection.setUnpublishedCollection(new ActionCollectionDTO());
|
||||
actionCollection.getUnpublishedCollection().setName("SampleJS");
|
||||
actionCollection.getUnpublishedCollection().setPageId(pageName);
|
||||
actionCollection.getUnpublishedCollection().setPluginId("js-plugin");
|
||||
actionCollection.getUnpublishedCollection().setPluginType(PluginType.JS);
|
||||
actionCollection.getUnpublishedCollection().setBody("export default {\\n\\t\\n}");
|
||||
actionCollectionList.add(actionCollection);
|
||||
}
|
||||
|
||||
applicationJson.setPageList(newPageList);
|
||||
applicationJson.setActionList(actionList);
|
||||
applicationJson.setActionCollectionList(actionCollectionList);
|
||||
return applicationJson;
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails("api_user")
|
||||
public void mergeApplicationJsonWithApplication_WhenPageNameConflicts_PageNamesRenamed() {
|
||||
String uniqueString = UUID.randomUUID().toString();
|
||||
|
||||
Application destApplication = new Application();
|
||||
destApplication.setName("App_" + uniqueString);
|
||||
destApplication.setSlug("my-slug");
|
||||
destApplication.setIsPublic(false);
|
||||
destApplication.setForkingEnabled(false);
|
||||
Mono<Application> createAppAndPageMono = applicationPageService.createApplication(destApplication, workspaceId)
|
||||
.flatMap(application -> {
|
||||
PageDTO pageDTO = new PageDTO();
|
||||
pageDTO.setName("Home");
|
||||
pageDTO.setApplicationId(application.getId());
|
||||
return applicationPageService.createPage(pageDTO).thenReturn(application);
|
||||
});
|
||||
|
||||
// let's create an ApplicationJSON which we'll merge with application created by createAppAndPageMono
|
||||
ApplicationJson applicationJson = createApplicationJSON(List.of("Home", "About"));
|
||||
|
||||
Mono<Tuple3<ApplicationPagesDTO, List<NewAction>, List<ActionCollection>>> tuple2Mono = createAppAndPageMono.flatMap(application ->
|
||||
// merge the application json with the application we've created
|
||||
importExportApplicationService.mergeApplicationJsonWithApplication(application.getOrganizationId(), application.getId(), null, applicationJson, null)
|
||||
.thenReturn(application)
|
||||
).flatMap(application ->
|
||||
// fetch the application pages, this should contain pages from application json
|
||||
Mono.zip(
|
||||
newPageService.findApplicationPages(application.getId(), null, null, ApplicationMode.EDIT),
|
||||
newActionService.findAllByApplicationIdAndViewMode(application.getId(), false, MANAGE_ACTIONS, null).collectList(),
|
||||
actionCollectionService.findAllByApplicationIdAndViewMode(application.getId(), false, MANAGE_ACTIONS, null).collectList()
|
||||
)
|
||||
);
|
||||
|
||||
StepVerifier.create(tuple2Mono).assertNext(objects -> {
|
||||
ApplicationPagesDTO applicationPagesDTO = objects.getT1();
|
||||
List<NewAction> newActionList = objects.getT2();
|
||||
List<ActionCollection> actionCollectionList = objects.getT3();
|
||||
|
||||
assertThat(applicationPagesDTO.getApplication().getName()).isEqualTo(destApplication.getName());
|
||||
assertThat(applicationPagesDTO.getApplication().getSlug()).isEqualTo(destApplication.getSlug());
|
||||
assertThat(applicationPagesDTO.getApplication().getIsPublic()).isFalse();
|
||||
assertThat(applicationPagesDTO.getApplication().getForkingEnabled()).isFalse();
|
||||
assertThat(applicationPagesDTO.getPages().size()).isEqualTo(4);
|
||||
List<String> pageNames = applicationPagesDTO.getPages().stream()
|
||||
.map(PageNameIdDTO::getName)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(pageNames).contains("Home", "Home2", "About");
|
||||
assertThat(newActionList.size()).isEqualTo(2); // we imported two pages and each page has one action
|
||||
assertThat(actionCollectionList.size()).isEqualTo(2); // we imported two pages and each page has one Collection
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails("api_user")
|
||||
public void mergeApplicationJsonWithApplication_WhenPageListIProvided_OnlyListedPagesAreMerged() {
|
||||
String uniqueString = UUID.randomUUID().toString();
|
||||
|
||||
Application destApplication = new Application();
|
||||
destApplication.setName("App_" + uniqueString);
|
||||
Mono<Application> createAppAndPageMono = applicationPageService.createApplication(destApplication, workspaceId)
|
||||
.flatMap(application -> {
|
||||
PageDTO pageDTO = new PageDTO();
|
||||
pageDTO.setName("Home");
|
||||
pageDTO.setApplicationId(application.getId());
|
||||
return applicationPageService.createPage(pageDTO).thenReturn(application);
|
||||
});
|
||||
|
||||
// let's create an ApplicationJSON which we'll merge with application created by createAppAndPageMono
|
||||
ApplicationJson applicationJson = createApplicationJSON(List.of("Profile", "About", "Contact US"));
|
||||
|
||||
Mono<ApplicationPagesDTO> applicationPagesDTOMono = createAppAndPageMono.flatMap(application ->
|
||||
// merge the application json with the application we've created
|
||||
importExportApplicationService.mergeApplicationJsonWithApplication(application.getOrganizationId(), application.getId(), null, applicationJson, List.of("About", "Contact US"))
|
||||
.thenReturn(application)
|
||||
).flatMap(application ->
|
||||
// fetch the application pages, this should contain pages from application json
|
||||
newPageService.findApplicationPages(application.getId(), null, null, ApplicationMode.EDIT)
|
||||
);
|
||||
|
||||
StepVerifier.create(applicationPagesDTOMono).assertNext(applicationPagesDTO -> {
|
||||
assertThat(applicationPagesDTO.getPages().size()).isEqualTo(4);
|
||||
List<String> pageNames = applicationPagesDTO.getPages().stream()
|
||||
.map(PageNameIdDTO::getName)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(pageNames).contains("Home", "About", "Contact US");
|
||||
assertThat(pageNames).doesNotContain("Profile");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void exportApplicationById_WhenThemeDoesNotExist_ExportedWithDefaultTheme() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user