Feature/import-export-application (#4553)
* Decryption for dbauth, basic and OAuth datasources added in exported file * All authentications for datasources included while exporting as deserialization is done through json file otherwise cast is throwing error * Content-Disposition header implemented * MongoEscapedWidget names segregated for published and unpublished layout, to prevent overwrite while exporting * Published pages and actions explicitly handled in cases where unpublished resources are deleted
This commit is contained in:
parent
771cf4a34f
commit
51addbc963
|
|
@ -0,0 +1,39 @@
|
|||
package com.appsmith.external.models;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* This class hold sensitive information, and fields that have a `@JsonIgnore` on them, so that such information
|
||||
* can be serialized when an application is exported.
|
||||
*/
|
||||
@ToString
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class DecryptedSensitiveFields {
|
||||
|
||||
String password;
|
||||
|
||||
String token;
|
||||
|
||||
String refreshToken;
|
||||
|
||||
Object tokenResponse;
|
||||
|
||||
String authType;
|
||||
|
||||
DBAuth dbAuth;
|
||||
|
||||
BasicAuth basicAuth;
|
||||
|
||||
OAuth2 openAuth2;
|
||||
|
||||
public DecryptedSensitiveFields(AuthenticationResponse authResponse) {
|
||||
this.token = authResponse.getToken();
|
||||
this.refreshToken = authResponse.getRefreshToken();
|
||||
this.tokenResponse = authResponse.getTokenResponse();
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ public class FieldName {
|
|||
public static String ANONYMOUS_USER = "anonymousUser";
|
||||
public static String USERNAMES = "usernames";
|
||||
public static String ACTION = "action";
|
||||
public static String ACTIONS = "actions";
|
||||
public static String ASSET = "asset";
|
||||
public static String APPLICATION = "application";
|
||||
public static String COMMENT = "comment";
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.appsmith.server.controllers;
|
|||
|
||||
import com.appsmith.server.constants.Url;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationJson;
|
||||
import com.appsmith.server.dtos.ApplicationAccessDTO;
|
||||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
import com.appsmith.server.dtos.UserHomepageDTO;
|
||||
|
|
@ -11,9 +12,15 @@ import com.appsmith.server.services.ApplicationPageService;
|
|||
import com.appsmith.server.services.ApplicationService;
|
||||
import com.appsmith.server.solutions.ApplicationFetcher;
|
||||
import com.appsmith.server.solutions.ApplicationForkingService;
|
||||
import com.appsmith.server.solutions.ImportExportApplicationService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
|
@ -22,12 +29,14 @@ import org.springframework.web.bind.annotation.PutMapping;
|
|||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(Url.APPLICATION_URL)
|
||||
|
|
@ -37,17 +46,20 @@ public class ApplicationController extends BaseController<ApplicationService, Ap
|
|||
private final ApplicationPageService applicationPageService;
|
||||
private final ApplicationFetcher applicationFetcher;
|
||||
private final ApplicationForkingService applicationForkingService;
|
||||
private final ImportExportApplicationService importExportApplicationService;
|
||||
|
||||
@Autowired
|
||||
public ApplicationController(
|
||||
ApplicationService service,
|
||||
ApplicationPageService applicationPageService,
|
||||
ApplicationFetcher applicationFetcher,
|
||||
ApplicationForkingService applicationForkingService) {
|
||||
ApplicationForkingService applicationForkingService,
|
||||
ImportExportApplicationService importExportApplicationService) {
|
||||
super(service);
|
||||
this.applicationPageService = applicationPageService;
|
||||
this.applicationFetcher = applicationFetcher;
|
||||
this.applicationForkingService = applicationForkingService;
|
||||
this.importExportApplicationService = importExportApplicationService;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
|
|
@ -117,4 +129,32 @@ public class ApplicationController extends BaseController<ApplicationService, Ap
|
|||
.map(application -> new ResponseDTO<>(HttpStatus.OK.value(), application, null));
|
||||
}
|
||||
|
||||
@GetMapping("/export/{id}")
|
||||
public Mono<ResponseEntity<ApplicationJson>> getApplicationFile(@PathVariable String id) {
|
||||
log.debug("Going to export application with id: {}", id);
|
||||
|
||||
return importExportApplicationService.exportApplicationById(id)
|
||||
.map(fetchedResource -> {
|
||||
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
ContentDisposition contentDisposition = ContentDisposition
|
||||
.builder("attachment")
|
||||
.filename("application-file.json", StandardCharsets.UTF_8)
|
||||
.build();
|
||||
responseHeaders.setContentDisposition(contentDisposition);
|
||||
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
return new ResponseEntity(fetchedResource, responseHeaders, HttpStatus.OK);
|
||||
});
|
||||
}
|
||||
|
||||
@PostMapping(value = "/import/{orgId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public Mono<ResponseDTO<Application>> importApplicationFromFile(
|
||||
@RequestPart("file") Mono<Part> fileMono, @PathVariable String orgId) {
|
||||
log.debug("Going to import application in organization with id: {}", orgId);
|
||||
return fileMono
|
||||
.flatMap(file -> importExportApplicationService.extractFileAndSaveApplication(orgId, file))
|
||||
.map(fetchedResource -> new ResponseDTO<>(HttpStatus.OK.value(), fetchedResource, null));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package com.appsmith.server.domains;
|
||||
|
||||
import com.appsmith.external.models.DecryptedSensitiveFields;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A DTO class to hold complete information about an application, which will then be serialized to a file so as to
|
||||
* export that application into a file.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class ApplicationJson {
|
||||
|
||||
Application exportedApplication;
|
||||
|
||||
List<Datasource> datasourceList;
|
||||
|
||||
List<NewPage> pageList;
|
||||
|
||||
String publishedDefaultPageName;
|
||||
|
||||
String unpublishedDefaultPageName;
|
||||
|
||||
List<NewAction> actionList;
|
||||
|
||||
Map<String, DecryptedSensitiveFields> decryptedFields;
|
||||
|
||||
/**
|
||||
* Mapping mongoEscapedWidgets with layoutId
|
||||
*/
|
||||
Map<String, Set<String>> publishedLayoutmongoEscapedWidgets;
|
||||
Map<String, Set<String>> unpublishedLayoutmongoEscapedWidgets;
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ import java.util.Set;
|
|||
public interface CustomDatasourceRepository extends AppsmithRepository<Datasource> {
|
||||
Flux<Datasource> findAllByOrganizationId(String organizationId, AclPermission permission);
|
||||
|
||||
Mono<Datasource> findByName(String name, AclPermission aclPermission);
|
||||
Mono<Datasource> findByNameAndOrganizationId(String name, String organizationId, AclPermission aclPermission);
|
||||
|
||||
Mono<Datasource> findById(String id, AclPermission aclPermission);
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,10 @@ public class CustomDatasourceRepositoryImpl extends BaseAppsmithRepositoryImpl<D
|
|||
}
|
||||
|
||||
@Override
|
||||
public Mono<Datasource> findByName(String name, AclPermission aclPermission) {
|
||||
public Mono<Datasource> findByNameAndOrganizationId(String name, String organizationId, AclPermission aclPermission) {
|
||||
Criteria nameCriteria = where(fieldName(QDatasource.datasource.name)).is(name);
|
||||
return queryOne(List.of(nameCriteria), aclPermission);
|
||||
Criteria orgIdCriteria = where(fieldName(QDatasource.datasource.organizationId)).is(organizationId);
|
||||
return queryOne(List.of(nameCriteria, orgIdCriteria), aclPermission);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -34,4 +34,6 @@ public interface ApplicationPageService {
|
|||
Mono<PageDTO> deleteUnpublishedPage(String id);
|
||||
|
||||
Mono<Boolean> publish(String applicationId);
|
||||
|
||||
void generateAndSetPagePolicies(Application application, PageDTO page);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ public class ApplicationPageServiceImpl implements ApplicationPageService {
|
|||
});
|
||||
}
|
||||
|
||||
private void generateAndSetPagePolicies(Application application, PageDTO page) {
|
||||
public void generateAndSetPagePolicies(Application application, PageDTO page) {
|
||||
Set<Policy> documentPolicies = policyGenerator.getAllChildPolicies(application.getPolicies(), Application.class, Page.class);
|
||||
page.setPolicies(documentPolicies);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public interface DatasourceService extends CrudService<Datasource, String> {
|
|||
|
||||
Mono<DatasourceTestResult> testDatasource(Datasource datasource);
|
||||
|
||||
Mono<Datasource> findByName(String name, AclPermission permission);
|
||||
Mono<Datasource> findByNameAndOrganizationId(String name, String organizationId, AclPermission permission);
|
||||
|
||||
Mono<Datasource> findById(String id, AclPermission aclPermission);
|
||||
|
||||
|
|
|
|||
|
|
@ -343,8 +343,8 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
|
|||
}
|
||||
|
||||
@Override
|
||||
public Mono<Datasource> findByName(String name, AclPermission permission) {
|
||||
return repository.findByName(name, permission);
|
||||
public Mono<Datasource> findByNameAndOrganizationId(String name, String organizationId, AclPermission permission) {
|
||||
return repository.findByNameAndOrganizationId(name, organizationId, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -459,7 +459,7 @@ public class ExamplesOrganizationCloner {
|
|||
});
|
||||
}
|
||||
|
||||
private void makePristine(BaseDomain domain) {
|
||||
public void makePristine(BaseDomain domain) {
|
||||
// Set the ID to null for this domain object so that it is saved a new document in the database (as opposed to
|
||||
// updating an existing document). If it contains any policies, they are also reset.
|
||||
domain.setId(null);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,640 @@
|
|||
package com.appsmith.server.solutions;
|
||||
|
||||
import com.appsmith.external.helpers.BeanCopyUtils;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
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.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.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationJson;
|
||||
import com.appsmith.server.domains.ApplicationPage;
|
||||
import com.appsmith.server.domains.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.ActionDTO;
|
||||
import com.appsmith.server.dtos.PageDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
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.ApplicationPageService;
|
||||
import com.appsmith.server.services.ApplicationService;
|
||||
import com.appsmith.server.services.DatasourceService;
|
||||
import com.appsmith.server.services.NewActionService;
|
||||
import com.appsmith.server.services.NewPageService;
|
||||
import com.appsmith.server.services.OrganizationService;
|
||||
import com.appsmith.server.services.SequenceService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
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.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ImportExportApplicationService {
|
||||
private final DatasourceService datasourceService;
|
||||
private final SessionUserService sessionUserService;
|
||||
private final NewActionRepository newActionRepository;
|
||||
private final DatasourceRepository datasourceRepository;
|
||||
private final PluginRepository pluginRepository;
|
||||
private final OrganizationService organizationService;
|
||||
private final ApplicationService applicationService;
|
||||
private final NewPageService newPageService;
|
||||
private final ApplicationPageService applicationPageService;
|
||||
private final NewPageRepository newPageRepository;
|
||||
private final NewActionService newActionService;
|
||||
private final SequenceService sequenceService;
|
||||
private final ExamplesOrganizationCloner examplesOrganizationCloner;
|
||||
|
||||
private static final Set<MediaType> ALLOWED_CONTENT_TYPES = Set.of(MediaType.APPLICATION_JSON);
|
||||
public final String INVALID_JSON_FILE = "invalid json file";
|
||||
private enum PublishType {
|
||||
UNPUBLISH, PUBLISH
|
||||
}
|
||||
|
||||
public Mono<ApplicationJson> exportApplicationById(String applicationId) {
|
||||
ApplicationJson applicationJson = new ApplicationJson();
|
||||
Map<String, String> pluginMap = new HashMap<>();
|
||||
Map<String, String> datasourceIdToNameMap = new HashMap<>();
|
||||
Map<String, String> pageIdToNameMap = new HashMap<>();
|
||||
|
||||
if (applicationId == null || applicationId.isEmpty()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.APPLICATION_ID));
|
||||
}
|
||||
|
||||
return pluginRepository
|
||||
.findAll()
|
||||
.map(plugin -> {
|
||||
pluginMap.put(plugin.getId(), plugin.getPackageName());
|
||||
return plugin;
|
||||
})
|
||||
.then(applicationService.findById(applicationId, AclPermission.MANAGE_APPLICATIONS))
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(
|
||||
AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION, applicationId))
|
||||
)
|
||||
.flatMap(application -> {
|
||||
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) {
|
||||
ApplicationPage publishedDefaultPage = application.getPublishedPages()
|
||||
.stream()
|
||||
.filter(ApplicationPage::getIsDefault)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if(publishedDefaultPage != null) {
|
||||
applicationJson.setPublishedDefaultPageName(publishedDefaultPage.getId());
|
||||
}
|
||||
}
|
||||
|
||||
final String organizationId = application.getOrganizationId();
|
||||
application.setOrganizationId(null);
|
||||
application.setPages(null);
|
||||
examplesOrganizationCloner.makePristine(application);
|
||||
applicationJson.setExportedApplication(application);
|
||||
return newPageRepository.findByApplicationId(applicationId, AclPermission.MANAGE_PAGES)
|
||||
.collectList()
|
||||
.flatMap(newPageList -> {
|
||||
Map<String, Set<String>> publishedMongoEscapedWidgetsNames = new HashMap<>();
|
||||
Map<String, Set<String>> unpublishedMongoEscapedWidgetsNames = new HashMap<>();
|
||||
newPageList.forEach(newPage -> {
|
||||
|
||||
if (newPage.getUnpublishedPage() != null) {
|
||||
pageIdToNameMap.put(
|
||||
newPage.getId() + PublishType.UNPUBLISH, 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.PUBLISH, 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()
|
||||
.map(newActionList -> {
|
||||
Set<String> 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 (newAction.getPluginType() == PluginType.DB || newAction.getPluginType() == PluginType.API) {
|
||||
concernedDBNames.add(
|
||||
mapDatasourceIdToNewAction(newAction.getPublishedAction(), datasourceIdToNameMap)
|
||||
);
|
||||
concernedDBNames.add(
|
||||
mapDatasourceIdToNewAction(newAction.getUnpublishedAction(), datasourceIdToNameMap)
|
||||
);
|
||||
}
|
||||
if (newAction.getUnpublishedAction() != null) {
|
||||
ActionDTO actionDTO = newAction.getUnpublishedAction();
|
||||
actionDTO.setPageId(pageIdToNameMap.get(actionDTO.getPageId() + PublishType.UNPUBLISH));
|
||||
}
|
||||
if (newAction.getPublishedAction() != null) {
|
||||
ActionDTO actionDTO = newAction.getPublishedAction();
|
||||
actionDTO.setPageId(pageIdToNameMap.get(actionDTO.getPageId() + PublishType.PUBLISH));
|
||||
}
|
||||
});
|
||||
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<String, DecryptedSensitiveFields> 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()));
|
||||
if (datasource.getDatasourceConfiguration() != null) {
|
||||
datasource.getDatasourceConfiguration().setAuthentication(null);
|
||||
}
|
||||
});
|
||||
applicationJson.setDecryptedFields(decryptedFields);
|
||||
return applicationJson;
|
||||
});
|
||||
})
|
||||
.then()
|
||||
.thenReturn(applicationJson);
|
||||
}
|
||||
|
||||
public Mono<Application> extractFileAndSaveApplication(String orgId, Part filePart) {
|
||||
|
||||
final MediaType contentType = filePart.headers().getContentType();
|
||||
|
||||
if (orgId == null || orgId.isEmpty()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ORGANIZATION_ID));
|
||||
}
|
||||
|
||||
if (contentType == null || !ALLOWED_CONTENT_TYPES.contains(contentType)) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.VALIDATION_FAILURE, INVALID_JSON_FILE));
|
||||
}
|
||||
|
||||
final Flux<DataBuffer> contentCache = filePart.content().cache();
|
||||
Mono<String> stringifiedFile = DataBufferUtils.join(contentCache)
|
||||
.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<ApplicationJson>() {}.getType();
|
||||
ApplicationJson jsonFile = gson.fromJson(data, fileType);
|
||||
return importApplicationInOrganization(orgId, jsonFile);
|
||||
});
|
||||
}
|
||||
|
||||
public Mono<Application> importApplicationInOrganization(String orgId, ApplicationJson importedDoc) {
|
||||
Map<String, String> pluginMap = new HashMap<>();
|
||||
Map<String, String> datasourceMap = new HashMap<>();
|
||||
Map<String, NewPage> pageNameMap = new HashMap<>();
|
||||
Map<String, String> actionIdMap = new HashMap<>();
|
||||
|
||||
Application importedApplication = importedDoc.getExportedApplication();
|
||||
List<Datasource> importedDatasourceList = importedDoc.getDatasourceList();
|
||||
List<NewPage> importedNewPageList = importedDoc.getPageList();
|
||||
List<NewAction> importedNewActionList = importedDoc.getActionList();
|
||||
|
||||
Mono<User> currUserMono = sessionUserService.getCurrentUser();
|
||||
final Flux<Datasource> existingDatasourceFlux = datasourceRepository.findAllByOrganizationId(orgId).cache();
|
||||
|
||||
String errorField = "";
|
||||
if (importedNewPageList == null || importedNewPageList.isEmpty()) {
|
||||
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));
|
||||
}
|
||||
|
||||
return pluginRepository.findAll()
|
||||
.map(plugin -> {
|
||||
pluginMap.put(plugin.getPackageName(), plugin.getId());
|
||||
return plugin;
|
||||
})
|
||||
.then(organizationService.findById(orgId, AclPermission.ORGANIZATION_MANAGE_APPLICATIONS))
|
||||
.switchIfEmpty(Mono.error(
|
||||
new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.ORGANIZATION, orgId))
|
||||
)
|
||||
.flatMap(organization -> Flux.fromIterable(importedDatasourceList)
|
||||
//Check for duplicate datasources to avoid duplicates in target organization
|
||||
.flatMap(datasource -> {
|
||||
datasource.setPluginId(pluginMap.get(datasource.getPluginId()));
|
||||
datasource.setOrganizationId(organization.getId());
|
||||
|
||||
//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, orgId);
|
||||
})
|
||||
.map(datasource -> {
|
||||
datasourceMap.put(datasource.getName(), datasource.getId());
|
||||
return datasource;
|
||||
})
|
||||
.collectList()
|
||||
)
|
||||
.then(
|
||||
|
||||
applicationPageService.setApplicationPolicies(currUserMono, orgId, importedApplication)
|
||||
.flatMap(application -> applicationService
|
||||
.findByOrganizationId(orgId, AclPermission.MANAGE_APPLICATIONS)
|
||||
.collectList()
|
||||
.flatMap(applicationList -> {
|
||||
|
||||
Application duplicateNameApp = applicationList
|
||||
.stream()
|
||||
.filter(application1 -> StringUtils.equals(application1.getName(), application.getName()))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
|
||||
return getUniqueSuffixForDuplicateNameEntity(duplicateNameApp, orgId)
|
||||
.map(suffix -> {
|
||||
importedApplication.setName(importedApplication.getName() + suffix);
|
||||
return importedApplication;
|
||||
});
|
||||
})
|
||||
.then(applicationService.save(importedApplication))
|
||||
)
|
||||
)
|
||||
.flatMap(savedApp -> {
|
||||
importedApplication.setId(savedApp.getId());
|
||||
importedNewPageList.forEach(newPage -> newPage.setApplicationId(savedApp.getId()));
|
||||
Map<PublishType, List<ApplicationPage>> applicationPages = Map.of(
|
||||
PublishType.UNPUBLISH, new ArrayList<>(),
|
||||
PublishType.PUBLISH, new ArrayList<>()
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
applicationPages.get(PublishType.UNPUBLISH).add(unpublishedAppPage);
|
||||
applicationPages.get(PublishType.PUBLISH).add(publishedAppPage);
|
||||
return applicationPages;
|
||||
})
|
||||
.then()
|
||||
.thenReturn(applicationPages);
|
||||
})
|
||||
.flatMap(applicationPageMap -> {
|
||||
importedApplication.setPages(applicationPageMap.get(PublishType.UNPUBLISH));
|
||||
importedApplication.setPublishedPages(applicationPageMap.get(PublishType.PUBLISH));
|
||||
|
||||
importedNewActionList.forEach(newAction -> {
|
||||
NewPage parentPage = new NewPage();
|
||||
if (newAction.getUnpublishedAction() != null && newAction.getUnpublishedAction().getName() != null) {
|
||||
parentPage = pageNameMap.get(newAction.getUnpublishedAction().getPageId());
|
||||
actionIdMap.put(newAction.getUnpublishedAction().getName(), newAction.getId());
|
||||
newAction.getUnpublishedAction().setPageId(parentPage.getId());
|
||||
mapDatasourceIdToNewAction(newAction.getUnpublishedAction(), datasourceMap);
|
||||
}
|
||||
|
||||
if (newAction.getPublishedAction() != null && newAction.getPublishedAction().getName() != null) {
|
||||
parentPage = pageNameMap.get(newAction.getPublishedAction().getPageId());
|
||||
actionIdMap.put(newAction.getPublishedAction().getName(), newAction.getId());
|
||||
newAction.getPublishedAction().setPageId(parentPage.getId());
|
||||
mapDatasourceIdToNewAction(newAction.getPublishedAction(), datasourceMap);
|
||||
}
|
||||
|
||||
examplesOrganizationCloner.makePristine(newAction);
|
||||
newAction.setOrganizationId(orgId);
|
||||
newAction.setApplicationId(importedApplication.getId());
|
||||
newAction.setPluginId(pluginMap.get(newAction.getPluginId()));
|
||||
newActionService.generateAndSetActionPolicies(parentPage, newAction);
|
||||
});
|
||||
return newActionService.saveAll(importedNewActionList)
|
||||
.map(newAction -> {
|
||||
|
||||
if (newAction.getUnpublishedAction() != null) {
|
||||
actionIdMap.put(
|
||||
actionIdMap.get(newAction.getUnpublishedAction().getName()), newAction.getId()
|
||||
);
|
||||
}
|
||||
|
||||
if (newAction.getPublishedAction() != null) {
|
||||
actionIdMap.put(
|
||||
actionIdMap.get(newAction.getPublishedAction().getName()), newAction.getId()
|
||||
);
|
||||
}
|
||||
|
||||
return newAction;
|
||||
})
|
||||
.then(Mono.just(importedApplication));
|
||||
})
|
||||
.flatMap(ignored -> {
|
||||
//Map layoutOnLoadActions ids with relevant actions
|
||||
importedNewPageList.forEach(page -> mapActionIdWithPageLayout(page, actionIdMap));
|
||||
return Flux.fromIterable(importedNewPageList)
|
||||
.flatMap(newPageService::save)
|
||||
.then(applicationService.update(importedApplication.getId(), importedApplication));
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<String> getUniqueSuffixForDuplicateNameEntity(BaseDomain sourceEntity, String orgId) {
|
||||
if (sourceEntity != null) {
|
||||
return sequenceService
|
||||
.getNextAsSuffix(sourceEntity.getClass(), " for organization with _id : " + orgId)
|
||||
.flatMap(sequenceNumber -> Mono.just(" #" + sequenceNumber.trim()));
|
||||
}
|
||||
return Mono.just("");
|
||||
}
|
||||
|
||||
private Flux<NewPage> importAndSavePages(List<NewPage> pages,
|
||||
Application application,
|
||||
Map<String, Set<String>> publishedMongoEscapedWidget,
|
||||
Map<String, Set<String>> unpublishedMongoEscapedWidget
|
||||
) {
|
||||
|
||||
pages.forEach(newPage -> {
|
||||
String layoutId = new ObjectId().toString();
|
||||
newPage.setApplicationId(application.getId());
|
||||
if (newPage.getUnpublishedPage() != null) {
|
||||
applicationPageService.generateAndSetPagePolicies(application, newPage.getUnpublishedPage());
|
||||
newPage.setPolicies(newPage.getUnpublishedPage().getPolicies());
|
||||
if (unpublishedMongoEscapedWidget != null) {
|
||||
newPage.getUnpublishedPage().getLayouts().forEach(layout -> {
|
||||
layout.setMongoEscapedWidgetNames(unpublishedMongoEscapedWidget.get(layout.getId()));
|
||||
layout.setId(layoutId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (newPage.getPublishedPage() != null) {
|
||||
applicationPageService.generateAndSetPagePolicies(application, newPage.getPublishedPage());
|
||||
if (publishedMongoEscapedWidget != null) {
|
||||
newPage.getPublishedPage().getLayouts().forEach(layout -> {
|
||||
layout.setMongoEscapedWidgetNames(publishedMongoEscapedWidget.get(layout.getId()));
|
||||
layout.setId(layoutId);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Flux.fromIterable(pages)
|
||||
.flatMap(newPageService::save);
|
||||
}
|
||||
|
||||
private String mapDatasourceIdToNewAction(ActionDTO actionDTO, Map<String, String> datasourceMap) {
|
||||
|
||||
if (actionDTO != null && actionDTO.getDatasource() != null && actionDTO.getDatasource().getId() != null) {
|
||||
|
||||
Datasource ds = actionDTO.getDatasource();
|
||||
//Mapping ds name in id field
|
||||
ds.setId(datasourceMap.get(ds.getId()));
|
||||
ds.setOrganizationId(null);
|
||||
ds.setPluginId(null);
|
||||
return ds.getId();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private void mapActionIdWithPageLayout(NewPage page, Map<String, String> actionIdMap) {
|
||||
if (page.getUnpublishedPage().getLayouts() != null) {
|
||||
|
||||
page.getUnpublishedPage().getLayouts().forEach(layout -> {
|
||||
if (layout.getLayoutOnLoadActions() != null) {
|
||||
layout.getLayoutOnLoadActions().forEach(onLoadAction -> onLoadAction
|
||||
.forEach(actionDTO -> actionDTO.setId(actionIdMap.get(actionDTO.getId()))));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (page.getPublishedPage() != null && page.getPublishedPage().getLayouts() != null) {
|
||||
|
||||
page.getPublishedPage().getLayouts().forEach(layout -> {
|
||||
if (layout.getLayoutOnLoadActions() != null) {
|
||||
layout.getLayoutOnLoadActions().forEach(onLoadAction -> onLoadAction
|
||||
.forEach(actionDTO -> actionDTO.setId(actionIdMap.get(actionDTO.getId()))));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Mono<Datasource> createUniqueDatasourceIfNotPresent(Flux<Datasource> existingDatasourceFlux,
|
||||
Datasource datasource,
|
||||
String toOrgId) {
|
||||
|
||||
final DatasourceConfiguration datasourceConfig = datasource.getDatasourceConfiguration();
|
||||
AuthenticationResponse authResponse = new AuthenticationResponse();
|
||||
if (datasourceConfig != null && datasourceConfig.getAuthentication() != null) {
|
||||
BeanCopyUtils.copyNestedNonNullProperties(
|
||||
datasourceConfig.getAuthentication().getAuthenticationResponse(), authResponse);
|
||||
datasourceConfig.getAuthentication().setAuthenticationResponse(null);
|
||||
datasourceConfig.getAuthentication().setAuthenticationType(null);
|
||||
}
|
||||
|
||||
return existingDatasourceFlux
|
||||
.map(ds -> {
|
||||
final DatasourceConfiguration dsAuthConfig = ds.getDatasourceConfiguration();
|
||||
if (dsAuthConfig != null && dsAuthConfig.getAuthentication() != null) {
|
||||
dsAuthConfig.getAuthentication().setAuthenticationResponse(null);
|
||||
dsAuthConfig.getAuthentication().setAuthenticationType(null);
|
||||
}
|
||||
return ds;
|
||||
})
|
||||
.filter(ds -> ds.softEquals(datasource))
|
||||
.next() // Get the first matching datasource, we don't need more than one here.
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
if (datasourceConfig != null && datasourceConfig.getAuthentication() != null) {
|
||||
datasourceConfig.getAuthentication().setAuthenticationResponse(authResponse);
|
||||
}
|
||||
// No matching existing datasource found, so create a new one.
|
||||
return datasourceService
|
||||
.findByNameAndOrganizationId(datasource.getName(), toOrgId, AclPermission.MANAGE_DATASOURCES)
|
||||
.flatMap(duplicateNameDatasource ->
|
||||
getUniqueSuffixForDuplicateNameEntity(duplicateNameDatasource, toOrgId)
|
||||
)
|
||||
.map(suffix -> {
|
||||
datasource.setName(datasource.getName() + suffix);
|
||||
return datasource;
|
||||
})
|
||||
.then(datasourceService.create(datasource));
|
||||
}));
|
||||
}
|
||||
|
||||
private Datasource updateAuthenticationDTO(Datasource datasource, DecryptedSensitiveFields decryptedFields) {
|
||||
|
||||
final DatasourceConfiguration dsConfig = datasource.getDatasourceConfiguration();
|
||||
String authType = decryptedFields.getAuthType();
|
||||
if (dsConfig == null || authType == null) {
|
||||
return datasource;
|
||||
}
|
||||
|
||||
if (StringUtils.equals(authType, DBAuth.class.getName())) {
|
||||
final DBAuth dbAuth = decryptedFields.getDbAuth();
|
||||
dbAuth.setPassword(decryptedFields.getPassword());
|
||||
datasource.getDatasourceConfiguration().setAuthentication(dbAuth);
|
||||
} else if (StringUtils.equals(authType, BasicAuth.class.getName())) {
|
||||
final BasicAuth basicAuth = decryptedFields.getBasicAuth();
|
||||
basicAuth.setPassword(decryptedFields.getPassword());
|
||||
datasource.getDatasourceConfiguration().setAuthentication(basicAuth);
|
||||
} else if (StringUtils.equals(authType, OAuth2.class.getName())) {
|
||||
OAuth2 auth2 = decryptedFields.getOpenAuth2();
|
||||
AuthenticationResponse authResponse = new AuthenticationResponse();
|
||||
auth2.setClientSecret(decryptedFields.getPassword());
|
||||
authResponse.setToken(decryptedFields.getToken());
|
||||
authResponse.setRefreshToken(decryptedFields.getRefreshToken());
|
||||
authResponse.setTokenResponse(decryptedFields.getTokenResponse());
|
||||
authResponse.setExpiresAt(Instant.now());
|
||||
datasource.getDatasourceConfiguration().setAuthentication(auth2);
|
||||
}
|
||||
return datasource;
|
||||
}
|
||||
|
||||
private DecryptedSensitiveFields getDecryptedFields(Datasource datasource) {
|
||||
final AuthenticationDTO authentication = datasource.getDatasourceConfiguration() == null
|
||||
? null : datasource.getDatasourceConfiguration().getAuthentication();
|
||||
|
||||
if (authentication != null) {
|
||||
DecryptedSensitiveFields dsDecryptedFields =
|
||||
authentication.getAuthenticationResponse() == null
|
||||
? new DecryptedSensitiveFields()
|
||||
: new DecryptedSensitiveFields(authentication.getAuthenticationResponse());
|
||||
|
||||
if (authentication instanceof DBAuth) {
|
||||
DBAuth auth = (DBAuth) authentication;
|
||||
dsDecryptedFields.setPassword(auth.getPassword());
|
||||
dsDecryptedFields.setDbAuth(auth);
|
||||
} else if (authentication instanceof OAuth2) {
|
||||
OAuth2 auth = (OAuth2) authentication;
|
||||
dsDecryptedFields.setPassword(auth.getClientSecret());
|
||||
dsDecryptedFields.setOpenAuth2(auth);
|
||||
} else if (authentication instanceof BasicAuth) {
|
||||
BasicAuth auth = (BasicAuth) authentication;
|
||||
dsDecryptedFields.setPassword(auth.getPassword());
|
||||
dsDecryptedFields.setBasicAuth(auth);
|
||||
}
|
||||
dsDecryptedFields.setAuthType(authentication.getClass().getName());
|
||||
return dsDecryptedFields;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -607,8 +607,10 @@ public class OrganizationServiceTest {
|
|||
Mono<Organization> readOrganizationByNameMono = organizationRepository.findByName("Member Management Admin Test Organization")
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "organization by name")));
|
||||
|
||||
Mono<Datasource> readDatasourceByNameMono = datasourceRepository.findByName("test datasource", READ_DATASOURCES)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "Datasource")));
|
||||
Mono<Datasource> readDatasourceByNameMono = organizationMono.flatMap(organization1 ->
|
||||
datasourceRepository.findByNameAndOrganizationId("test datasource", organization1.getId(),READ_DATASOURCES)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "Datasource")))
|
||||
);
|
||||
|
||||
Mono<Tuple3<Application, Organization, Datasource>> testMono = organizationMono
|
||||
// create application and datasource
|
||||
|
|
|
|||
|
|
@ -0,0 +1,534 @@
|
|||
package com.appsmith.server.solutions;
|
||||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
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.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationJson;
|
||||
import com.appsmith.server.domains.ApplicationPage;
|
||||
import com.appsmith.server.domains.Datasource;
|
||||
import com.appsmith.server.domains.Layout;
|
||||
import com.appsmith.server.domains.NewAction;
|
||||
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.domains.User;
|
||||
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.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.ApplicationPageService;
|
||||
import com.appsmith.server.services.ApplicationService;
|
||||
import com.appsmith.server.services.DatasourceService;
|
||||
import com.appsmith.server.services.LayoutActionService;
|
||||
import com.appsmith.server.services.NewActionService;
|
||||
import com.appsmith.server.services.NewPageService;
|
||||
import com.appsmith.server.services.OrganizationService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.UserService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.minidev.json.JSONArray;
|
||||
import net.minidev.json.JSONObject;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.multipart.FilePart;
|
||||
import org.springframework.security.test.context.support.WithUserDetails;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
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;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_PAGES;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Slf4j
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@DirtiesContext
|
||||
public class ImportExportApplicationServiceTests {
|
||||
|
||||
@Autowired
|
||||
private ImportExportApplicationService importExportApplicationService;
|
||||
|
||||
@Autowired
|
||||
private ApplicationPageService applicationPageService;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private PluginRepository pluginRepository;
|
||||
|
||||
@Autowired
|
||||
private ApplicationService applicationService;
|
||||
|
||||
@Autowired
|
||||
private DatasourceService datasourceService;
|
||||
|
||||
@Autowired
|
||||
private NewPageService newPageService;
|
||||
|
||||
@Autowired
|
||||
private NewActionService newActionService;
|
||||
|
||||
@Autowired
|
||||
private OrganizationService organizationService;
|
||||
|
||||
@Autowired
|
||||
private SessionUserService sessionUserService;
|
||||
|
||||
@Autowired
|
||||
private LayoutActionService layoutActionService;
|
||||
|
||||
@Autowired
|
||||
private NewPageRepository newPageRepository;
|
||||
|
||||
@MockBean
|
||||
private PluginExecutorHelper pluginExecutorHelper;
|
||||
|
||||
private String invalid_json_file;
|
||||
private Plugin installedPlugin;
|
||||
private String orgId;
|
||||
private String testAppId;
|
||||
private Map<String, Datasource> datasourceMap = new HashMap<>();
|
||||
|
||||
private Flux<ActionDTO> getActionsInApplication(Application application) {
|
||||
return newPageService
|
||||
// fetch the unpublished pages
|
||||
.findByApplicationId(application.getId(), READ_PAGES, false)
|
||||
.flatMap(page -> newActionService.getUnpublishedActions(new LinkedMultiValueMap<>(
|
||||
Map.of(FieldName.PAGE_ID, Collections.singletonList(page.getId())))));
|
||||
}
|
||||
|
||||
@Before
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void setup() {
|
||||
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor()));
|
||||
installedPlugin = pluginRepository.findByPackageName("installed-plugin").block();
|
||||
User apiUser = userService.findByEmail("api_user").block();
|
||||
orgId = apiUser.getOrganizationIds().iterator().next();
|
||||
|
||||
invalid_json_file = importExportApplicationService.INVALID_JSON_FILE;
|
||||
|
||||
Datasource ds1 = new Datasource();
|
||||
ds1.setName("DS1");
|
||||
ds1.setOrganizationId(orgId);
|
||||
ds1.setPluginId(installedPlugin.getId());
|
||||
final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
ds1.setDatasourceConfiguration(datasourceConfiguration);
|
||||
datasourceConfiguration.setUrl("http://httpbin.org/get");
|
||||
datasourceConfiguration.setHeaders(List.of(
|
||||
new Property("X-Answer", "42")
|
||||
));
|
||||
|
||||
final Datasource ds2 = new Datasource();
|
||||
ds2.setName("DS2");
|
||||
ds2.setPluginId(installedPlugin.getId());
|
||||
ds2.setDatasourceConfiguration(new DatasourceConfiguration());
|
||||
DBAuth auth = new DBAuth();
|
||||
auth.setPassword("awesome-password");
|
||||
ds2.getDatasourceConfiguration().setAuthentication(auth);
|
||||
|
||||
datasourceMap.put("DS1", ds1);
|
||||
datasourceMap.put("DS2", ds2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exportApplicationWithNullApplicationIdTest() {
|
||||
Mono<ApplicationJson> resultMono = importExportApplicationService.exportApplicationById(null);
|
||||
|
||||
StepVerifier
|
||||
.create(resultMono)
|
||||
.expectErrorMatches(throwable -> throwable instanceof AppsmithException &&
|
||||
throwable.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage(FieldName.APPLICATION_ID)))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void createExportAppJsonWithoutActionsAndDatasourceTest() {
|
||||
|
||||
Application testApplication = new Application();
|
||||
testApplication.setName("Export Application TestApp");
|
||||
|
||||
final Mono<ApplicationJson> resultMono = applicationPageService.createApplication(testApplication, orgId)
|
||||
.flatMap(application -> importExportApplicationService.exportApplicationById(application.getId()));
|
||||
|
||||
StepVerifier.create(resultMono)
|
||||
.assertNext(applicationJson -> {
|
||||
Application exportedApp = applicationJson.getExportedApplication();
|
||||
List<NewPage> pageList = applicationJson.getPageList();
|
||||
List<NewAction> actionList = applicationJson.getActionList();
|
||||
List<Datasource> datasourceList = applicationJson.getDatasourceList();
|
||||
|
||||
NewPage defaultPage = pageList.get(0);
|
||||
|
||||
assertThat(exportedApp.getName()).isEqualTo(testApplication.getName());
|
||||
assertThat(exportedApp.getOrganizationId()).isNull();
|
||||
assertThat(exportedApp.getPages()).isNull();
|
||||
assertThat(exportedApp.getPolicies().size()).isEqualTo(0);
|
||||
|
||||
assertThat(pageList.isEmpty()).isFalse();
|
||||
assertThat(defaultPage.getApplicationId()).isNull();
|
||||
assertThat(defaultPage.getUnpublishedPage().getLayouts().get(0).getLayoutOnLoadActions()).isNull();
|
||||
|
||||
assertThat(actionList.isEmpty()).isTrue();
|
||||
|
||||
assertThat(datasourceList.isEmpty()).isTrue();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void createExportAppJsonWithDatasourceButWithoutActionsTest() {
|
||||
|
||||
Application testApplication = new Application();
|
||||
testApplication.setName("Another Export Application");
|
||||
|
||||
final Mono<ApplicationJson> resultMono = organizationService.getById(orgId)
|
||||
.flatMap(organization -> {
|
||||
|
||||
final Datasource ds1 = datasourceMap.get("DS1");
|
||||
ds1.setOrganizationId(organization.getId());
|
||||
|
||||
final Datasource ds2 = datasourceMap.get("DS2");
|
||||
ds2.setOrganizationId(organization.getId());
|
||||
|
||||
return Mono.zip(
|
||||
datasourceService.create(ds1),
|
||||
datasourceService.create(ds2),
|
||||
applicationPageService.createApplication(testApplication, orgId)
|
||||
);
|
||||
})
|
||||
.flatMap(tuple -> importExportApplicationService.exportApplicationById(tuple.getT3().getId()));
|
||||
|
||||
StepVerifier.create(resultMono)
|
||||
.assertNext(applicationJson -> {
|
||||
|
||||
assertThat(applicationJson.getPageList()).hasSize(1);
|
||||
assertThat(applicationJson.getActionList()).isEmpty();
|
||||
assertThat(applicationJson.getDatasourceList()).isEmpty();
|
||||
assertThat(applicationJson.getDecryptedFields()).isEmpty();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void createExportAppJsonWithActionsAndDatasourceTest() {
|
||||
|
||||
Organization newOrganization = new Organization();
|
||||
newOrganization.setName("template-org-with-ds");
|
||||
|
||||
Application testApplication = new Application();
|
||||
testApplication.setName("ApplicationWithActionsAndDatasource");
|
||||
|
||||
final Mono<ApplicationJson> resultMono = organizationService.create(newOrganization)
|
||||
.zipWhen(org -> applicationPageService.createApplication(testApplication, org.getId()))
|
||||
.flatMap(tuple -> {
|
||||
|
||||
Organization organization = tuple.getT1();
|
||||
Application testApp = tuple.getT2();
|
||||
|
||||
final Datasource ds1 = datasourceMap.get("DS1");
|
||||
ds1.setOrganizationId(organization.getId());
|
||||
|
||||
final Datasource ds2 = datasourceMap.get("DS2");
|
||||
ds2.setOrganizationId(organization.getId());
|
||||
|
||||
final String pageId = testApp.getPages().get(0).getId();
|
||||
|
||||
return Mono.zip(
|
||||
datasourceService.create(ds1),
|
||||
datasourceService.create(ds2),
|
||||
Mono.just(testApp),
|
||||
newPageService.findPageById(pageId, READ_PAGES, false)
|
||||
);
|
||||
})
|
||||
.flatMap(tuple -> {
|
||||
|
||||
Datasource ds1 = tuple.getT1();
|
||||
Datasource ds2 = tuple.getT2();
|
||||
Application testApp = tuple.getT3();
|
||||
PageDTO testPage = tuple.getT4();
|
||||
|
||||
Layout layout = testPage.getLayouts().get(0);
|
||||
JSONObject dsl = new JSONObject(Map.of("text", "{{ query1.data }}"));
|
||||
|
||||
JSONObject dsl2 = new JSONObject();
|
||||
dsl2.put("widgetName", "Table1");
|
||||
dsl2.put("type", "TABLE_WIDGET");
|
||||
Map<String, Object> primaryColumns = new HashMap<>();
|
||||
JSONObject jsonObject = new JSONObject(Map.of("key", "value"));
|
||||
primaryColumns.put("_id", "{{ query1.data }}");
|
||||
primaryColumns.put("_class", jsonObject);
|
||||
dsl2.put("primaryColumns", primaryColumns);
|
||||
final ArrayList<Object> objects = new ArrayList<>();
|
||||
JSONArray temp2 = new JSONArray();
|
||||
temp2.addAll(List.of(new JSONObject(Map.of("key", "primaryColumns._id"))));
|
||||
dsl2.put("dynamicBindingPathList", temp2);
|
||||
objects.add(dsl2);
|
||||
dsl.put("children", objects);
|
||||
|
||||
layout.setDsl(dsl);
|
||||
layout.setPublishedDsl(dsl);
|
||||
|
||||
ActionDTO action = new ActionDTO();
|
||||
action.setName("validAction");
|
||||
action.setPageId(testPage.getId());
|
||||
action.setExecuteOnLoad(true);
|
||||
ActionConfiguration actionConfiguration = new ActionConfiguration();
|
||||
actionConfiguration.setHttpMethod(HttpMethod.GET);
|
||||
action.setActionConfiguration(actionConfiguration);
|
||||
action.setDatasource(ds2);
|
||||
|
||||
return layoutActionService.createAction(action)
|
||||
.flatMap(createdAction -> newActionService.findById(createdAction.getId(), READ_ACTIONS))
|
||||
.flatMap(newAction -> newActionService.generateActionByViewMode(newAction, false))
|
||||
.then(importExportApplicationService.exportApplicationById(testApp.getId()));
|
||||
});
|
||||
|
||||
StepVerifier
|
||||
.create(resultMono)
|
||||
.assertNext(applicationJson -> {
|
||||
|
||||
Application exportedApp = applicationJson.getExportedApplication();
|
||||
List<NewPage> pageList = applicationJson.getPageList();
|
||||
List<NewAction> actionList = applicationJson.getActionList();
|
||||
List<Datasource> datasourceList = applicationJson.getDatasourceList();
|
||||
|
||||
NewPage defaultPage = pageList.get(0);
|
||||
|
||||
assertThat(exportedApp.getName()).isEqualTo(testApplication.getName());
|
||||
assertThat(exportedApp.getOrganizationId()).isNull();
|
||||
assertThat(exportedApp.getPages()).isNull();
|
||||
|
||||
assertThat(exportedApp.getPolicies()).hasSize(0);
|
||||
|
||||
assertThat(pageList).hasSize(1);
|
||||
assertThat(defaultPage.getApplicationId()).isNull();
|
||||
assertThat(defaultPage.getUnpublishedPage().getLayouts().get(0).getDsl()).isNotNull();
|
||||
assertThat(defaultPage.getId()).isNull();
|
||||
assertThat(defaultPage.getPolicies()).isEmpty();
|
||||
|
||||
assertThat(actionList.isEmpty()).isFalse();
|
||||
NewAction validAction = actionList.get(0);
|
||||
assertThat(validAction.getApplicationId()).isNull();
|
||||
assertThat(validAction.getPluginId()).isEqualTo(installedPlugin.getPackageName());
|
||||
assertThat(validAction.getPluginType()).isEqualTo(PluginType.API);
|
||||
assertThat(validAction.getOrganizationId()).isNull();
|
||||
assertThat(validAction.getPolicies()).isNull();
|
||||
assertThat(validAction.getId()).isNotNull();
|
||||
assertThat(validAction.getUnpublishedAction().getPageId())
|
||||
.isEqualTo(defaultPage.getUnpublishedPage().getName());
|
||||
|
||||
assertThat(datasourceList).hasSize(1);
|
||||
Datasource datasource = datasourceList.get(0);
|
||||
assertThat(datasource.getOrganizationId()).isNull();
|
||||
assertThat(datasource.getId()).isNull();
|
||||
assertThat(datasource.getPluginId()).isEqualTo(installedPlugin.getPackageName());
|
||||
assertThat(datasource.getDatasourceConfiguration().getAuthentication()).isNull();
|
||||
|
||||
DecryptedSensitiveFields decryptedFields =
|
||||
applicationJson.getDecryptedFields().get(datasource.getName());
|
||||
|
||||
DBAuth auth = (DBAuth) datasourceMap.get("DS2").getDatasourceConfiguration().getAuthentication();
|
||||
assertThat(decryptedFields.getAuthType()).isEqualTo(auth.getClass().getName());
|
||||
assertThat(decryptedFields.getPassword()).isEqualTo("awesome-password");
|
||||
|
||||
assertThat(applicationJson.getUnpublishedLayoutmongoEscapedWidgets()).isNotEmpty();
|
||||
assertThat(applicationJson.getPublishedLayoutmongoEscapedWidgets()).isNotEmpty();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importApplicationFromInvalidFileTest() {
|
||||
FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
Flux<DataBuffer> dataBufferFlux = DataBufferUtils
|
||||
.read(new ClassPathResource("test_assets/OrganizationServiceTest/my_organization_logo.png"), new DefaultDataBufferFactory(), 4096)
|
||||
.cache();
|
||||
|
||||
Mockito.when(filepart.content()).thenReturn(dataBufferFlux);
|
||||
Mockito.when(filepart.headers().getContentType()).thenReturn(MediaType.IMAGE_PNG);
|
||||
|
||||
Mono<Application> resultMono = importExportApplicationService.extractFileAndSaveApplication(orgId, filepart);
|
||||
|
||||
StepVerifier
|
||||
.create(resultMono)
|
||||
.expectErrorMatches(error -> error instanceof AppsmithException)
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importApplicationWithNullOrganizationIdTest() {
|
||||
FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
|
||||
Mono<Application> resultMono = importExportApplicationService
|
||||
.extractFileAndSaveApplication(null, filepart);
|
||||
|
||||
StepVerifier
|
||||
.create(resultMono)
|
||||
.expectErrorMatches(throwable -> throwable instanceof AppsmithException &&
|
||||
throwable.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage(FieldName.ORGANIZATION_ID)))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void importApplicationFromInvalidJsonFileWithoutPagesTest() {
|
||||
|
||||
FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/invalid-json-without-pages.json");
|
||||
Mono<Application> resultMono = importExportApplicationService.extractFileAndSaveApplication(orgId,filePart);
|
||||
|
||||
StepVerifier
|
||||
.create(resultMono)
|
||||
.expectErrorMatches(throwable -> throwable instanceof AppsmithException &&
|
||||
throwable.getMessage().equals(AppsmithError.NO_RESOURCE_FOUND.getMessage(FieldName.PAGES, invalid_json_file)))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void importApplicationFromInvalidJsonFileWithoutApplicationTest() {
|
||||
|
||||
FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/invalid-json-without-app.json");
|
||||
Mono<Application> resultMono = importExportApplicationService.extractFileAndSaveApplication(orgId,filePart);
|
||||
|
||||
StepVerifier
|
||||
.create(resultMono)
|
||||
.expectErrorMatches(throwable -> throwable instanceof AppsmithException &&
|
||||
throwable.getMessage().equals(AppsmithError.NO_RESOURCE_FOUND.getMessage(FieldName.APPLICATION, invalid_json_file)))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void importApplicationFromValidJsonFileTest() {
|
||||
|
||||
FilePart filePart = createFilePart("test_assets/ImportExportServiceTest/valid-application.json");
|
||||
|
||||
Organization newOrganization = new Organization();
|
||||
newOrganization.setName("Template Organization");
|
||||
|
||||
Policy manageAppPolicy = Policy.builder().permission(MANAGE_APPLICATIONS.getValue())
|
||||
.users(Set.of("api_user"))
|
||||
.build();
|
||||
Policy readAppPolicy = Policy.builder().permission(READ_APPLICATIONS.getValue())
|
||||
.users(Set.of("api_user"))
|
||||
.build();
|
||||
|
||||
final Mono<Application> resultMono = organizationService
|
||||
.create(newOrganization)
|
||||
.flatMap(organization -> importExportApplicationService
|
||||
.extractFileAndSaveApplication(organization.getId(), filePart)
|
||||
);
|
||||
|
||||
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()
|
||||
)))
|
||||
.assertNext(tuple -> {
|
||||
final Application application = tuple.getT1();
|
||||
final List<Datasource> datasourceList = tuple.getT2();
|
||||
final List<ActionDTO> actionDTOS = tuple.getT3();
|
||||
final List<PageDTO> pageList = tuple.getT4();
|
||||
|
||||
assertThat(application.getName()).isEqualTo("valid_application");
|
||||
assertThat(application.getOrganizationId()).isNotNull();
|
||||
assertThat(application.getPages()).isNotEmpty();
|
||||
assertThat(application.getPolicies()).containsAll(Set.of(manageAppPolicy, readAppPolicy));
|
||||
|
||||
assertThat(datasourceList).isNotEmpty();
|
||||
datasourceList.forEach(datasource -> {
|
||||
assertThat(datasource.getOrganizationId()).isEqualTo(application.getOrganizationId());
|
||||
if (datasource.getName().contains("wo-auth")) {
|
||||
assertThat(datasource.getDatasourceConfiguration().getAuthentication()).isNull();
|
||||
} else if (datasource.getName().contains("db")) {
|
||||
DBAuth auth = (DBAuth) datasource.getDatasourceConfiguration().getAuthentication();
|
||||
assertThat(auth).isNotNull();
|
||||
assertThat(auth.getPassword()).isNotNull();
|
||||
assertThat(auth.getUsername()).isNotNull();
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(actionDTOS).isNotEmpty();
|
||||
actionDTOS.forEach(actionDTO -> {
|
||||
assertThat(actionDTO.getPageId()).isNotEqualTo(pageList.get(0).getName());
|
||||
|
||||
});
|
||||
|
||||
assertThat(pageList).isNotEmpty();
|
||||
|
||||
ApplicationPage defaultAppPage = application.getPages()
|
||||
.stream()
|
||||
.filter(ApplicationPage::getIsDefault)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
assertThat(defaultAppPage).isNotNull();
|
||||
|
||||
PageDTO defaultPageDTO = pageList.stream()
|
||||
.filter(pageDTO -> pageDTO.getId().equals(defaultAppPage.getId())).findFirst().orElse(null);
|
||||
|
||||
assertThat(defaultPageDTO).isNotNull();
|
||||
assertThat(defaultPageDTO.getLayouts().get(0).getLayoutOnLoadActions()).isNotEmpty();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
private FilePart createFilePart(String filePath) {
|
||||
FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
Flux<DataBuffer> dataBufferFlux = DataBufferUtils
|
||||
.read(
|
||||
new ClassPathResource(filePath),
|
||||
new DefaultDataBufferFactory(),
|
||||
4096)
|
||||
.cache();
|
||||
|
||||
Mockito.when(filepart.content()).thenReturn(dataBufferFlux);
|
||||
Mockito.when(filepart.headers().getContentType()).thenReturn(MediaType.APPLICATION_JSON);
|
||||
|
||||
return filepart;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"datasourceList": [],
|
||||
"pageList": [
|
||||
{
|
||||
"applicationId": "test_app"
|
||||
}
|
||||
],
|
||||
"actionList": [
|
||||
{
|
||||
"id": "randomId",
|
||||
"userPermissions": [],
|
||||
"unpublishedAction": {},
|
||||
"publishedAction": {}
|
||||
}
|
||||
],
|
||||
"decryptedFields": {},
|
||||
"mongoEscapedWidgets": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"exportedApplication": {},
|
||||
"datasourceList": [],
|
||||
"pageList": [],
|
||||
"actionList": [
|
||||
{
|
||||
"id": "randomId"
|
||||
}
|
||||
],
|
||||
"decryptedFields": {},
|
||||
"mongoEscapedWidgets": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,505 @@
|
|||
{
|
||||
"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": [
|
||||
"execute:datasources",
|
||||
"manage:datasources",
|
||||
"read:datasources"
|
||||
],
|
||||
"name": "db-auth",
|
||||
"pluginId": "mongo-plugin",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"userPermissions": [
|
||||
"execute:datasources",
|
||||
"manage:datasources",
|
||||
"read:datasources"
|
||||
],
|
||||
"name": "api_ds_wo_auth",
|
||||
"pluginId": "restapi-plugin",
|
||||
"datasourceConfiguration": {
|
||||
"sshProxyEnabled": false,
|
||||
"properties": [
|
||||
{
|
||||
"key": "isSendSessionEnabled",
|
||||
"value": "N"
|
||||
},
|
||||
{
|
||||
"key": "sessionSignatureKey",
|
||||
"value": ""
|
||||
}
|
||||
],
|
||||
"url": "https://api-ds-wo-auth-uri.com",
|
||||
"headers": []
|
||||
},
|
||||
"invalids": [],
|
||||
"isValid": true,
|
||||
"new": true
|
||||
}
|
||||
],
|
||||
"pageList": [
|
||||
{
|
||||
"userPermissions": [
|
||||
"read:pages",
|
||||
"manage:pages"
|
||||
],
|
||||
"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) => { return 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) => { return 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) => { return 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
|
||||
}
|
||||
],
|
||||
"actionList": [
|
||||
{
|
||||
"id": "60aca092136c4b7178f6790a",
|
||||
"userPermissions": [],
|
||||
"applicationId": "valid_application",
|
||||
"pluginType": "DB",
|
||||
"pluginId": "mongo-plugin",
|
||||
"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": [],
|
||||
"isValid": true,
|
||||
"invalids": [],
|
||||
"jsonPathKeys": [],
|
||||
"confirmBeforeExecute": false,
|
||||
"userPermissions": []
|
||||
},
|
||||
"publishedAction": {
|
||||
"datasource": {
|
||||
"userPermissions": [],
|
||||
"isValid": true,
|
||||
"new": true
|
||||
},
|
||||
"confirmBeforeExecute": false,
|
||||
"userPermissions": []
|
||||
},
|
||||
"new": false
|
||||
},
|
||||
{
|
||||
"id": "60aca24c136c4b7178f6790d",
|
||||
"userPermissions": [],
|
||||
"applicationId": "valid_application",
|
||||
"pluginType": "API",
|
||||
"pluginId": "restapi-plugin",
|
||||
"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": [],
|
||||
"isValid": true,
|
||||
"invalids": [],
|
||||
"jsonPathKeys": [],
|
||||
"confirmBeforeExecute": false,
|
||||
"userPermissions": []
|
||||
},
|
||||
"publishedAction": {
|
||||
"datasource": {
|
||||
"userPermissions": [],
|
||||
"isValid": true,
|
||||
"new": true
|
||||
},
|
||||
"confirmBeforeExecute": false,
|
||||
"userPermissions": []
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -23,4 +23,4 @@ APPSMITH_CODEC_SIZE=10
|
|||
#APPSMITH_SENTRY_ENVIRONMENT=
|
||||
|
||||
#APPSMITH_RECAPTCHA_SITE_KEY=""
|
||||
#APPSMITH_RECAPTCHA_SECRET_KEY=""
|
||||
#APPSMITH_RECAPTCHA_SECRET_KEY=""
|
||||
Loading…
Reference in New Issue
Block a user