Merge branch 'release' of https://github.com/appsmithorg/appsmith into release

This commit is contained in:
Automated Github Action 2020-08-11 09:05:10 +00:00
commit 56f681dc16
13 changed files with 322 additions and 68 deletions

View File

@ -241,11 +241,9 @@ public class RestApiPlugin extends BasePlugin {
));
}
Object requestBodyAsObject = null;
if (MediaType.APPLICATION_JSON_VALUE.equals(contentType)) {
try {
requestBodyAsObject = objectFromJson(requestBodyAsString);
objectFromJson(requestBodyAsString);
} catch (JsonSyntaxException e) {
return Mono.error(new AppsmithPluginException(
AppsmithPluginError.PLUGIN_ERROR,
@ -254,14 +252,10 @@ public class RestApiPlugin extends BasePlugin {
}
}
if (requestBodyAsObject == null) {
requestBodyAsObject = requestBodyAsString;
}
return webClient
.method(httpMethod)
.uri(uri)
.body(BodyInserters.fromObject(requestBodyAsObject))
.body(BodyInserters.fromObject(requestBodyAsString))
.exchange()
.doOnError(e -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)))
.flatMap(response -> {

View File

@ -7,6 +7,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;
import javax.validation.constraints.NotNull;
@ -29,4 +30,8 @@ public class Application extends BaseDomain {
Boolean isPublic = false;
List<ApplicationPage> pages;
@Transient
boolean appIsExample = false;
}

View File

@ -35,7 +35,7 @@ import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.READ_PAGES;
@Component
public class PolicyUtils<T extends BaseDomain> {
public class PolicyUtils {
private final PolicyGenerator policyGenerator;
private final ApplicationRepository applicationRepository;
@ -55,7 +55,7 @@ public class PolicyUtils<T extends BaseDomain> {
this.datasourceRepository = datasourceRepository;
}
public T addPoliciesToExistingObject(Map<String, Policy> policyMap, T obj) {
public <T extends BaseDomain> T addPoliciesToExistingObject(Map<String, Policy> policyMap, T obj) {
// Making a deep copy here so we don't modify the `policyMap` object.
// TODO: Investigate a solution without using deep-copy.
final Map<String, Policy> policyMap1 = new HashMap<>();
@ -83,7 +83,7 @@ public class PolicyUtils<T extends BaseDomain> {
return obj;
}
public T removePoliciesFromExistingObject(Map<String, Policy> policyMap, T obj) {
public <T extends BaseDomain> T removePoliciesFromExistingObject(Map<String, Policy> policyMap, T obj) {
// Making a deep copy here so we don't modify the `policyMap` object.
// TODO: Investigate a solution without using deep-copy.
final Map<String, Policy> policyMap1 = new HashMap<>();
@ -168,9 +168,9 @@ public class PolicyUtils<T extends BaseDomain> {
.switchIfEmpty(Mono.empty())
.map(datasource -> {
if (addPolicyToObject) {
return (Datasource) addPoliciesToExistingObject(newPoliciesMap, (T) datasource);
return addPoliciesToExistingObject(newPoliciesMap, datasource);
} else {
return (Datasource) removePoliciesFromExistingObject(newPoliciesMap, (T) datasource);
return removePoliciesFromExistingObject(newPoliciesMap, datasource);
}
})
.collectList()
@ -185,9 +185,9 @@ public class PolicyUtils<T extends BaseDomain> {
.switchIfEmpty(Mono.empty())
.map(application -> {
if (addPolicyToObject) {
return (Application) addPoliciesToExistingObject(newAppPoliciesMap, (T) application);
return addPoliciesToExistingObject(newAppPoliciesMap, application);
} else {
return (Application) removePoliciesFromExistingObject(newAppPoliciesMap, (T) application);
return removePoliciesFromExistingObject(newAppPoliciesMap, application);
}
})
.collectList()
@ -213,9 +213,9 @@ public class PolicyUtils<T extends BaseDomain> {
.switchIfEmpty(Mono.empty())
.map(page -> {
if (addPolicyToObject) {
return (Page) addPoliciesToExistingObject(newPagePoliciesMap, (T) page);
return addPoliciesToExistingObject(newPagePoliciesMap, page);
} else {
return (Page) removePoliciesFromExistingObject(newPagePoliciesMap, (T) page);
return removePoliciesFromExistingObject(newPagePoliciesMap, page);
}
})
.collectList()
@ -241,9 +241,9 @@ public class PolicyUtils<T extends BaseDomain> {
.switchIfEmpty(Mono.empty())
.map(action -> {
if (addPolicyToObject) {
return (Action) addPoliciesToExistingObject(newActionPoliciesMap, (T) action);
return addPoliciesToExistingObject(newActionPoliciesMap, action);
} else {
return (Action) removePoliciesFromExistingObject(newActionPoliciesMap, (T) action);
return removePoliciesFromExistingObject(newActionPoliciesMap, action);
}
})
.map(action -> {

View File

@ -19,4 +19,6 @@ public interface ActionRepository extends BaseRepository<Action, String>, Custom
Mono<Long> countByDatasourceId(String datasourceId);
Flux<Action> findByPageId(String pageId);
Flux<Action> findByOrganizationId(String organizationId);
}

View File

@ -48,6 +48,7 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
private final PageRepository pageRepository;
private final PolicyUtils policyUtils;
private final DatasourceService datasourceService;
private final ConfigService configService;
@Autowired
public ApplicationServiceImpl(Scheduler scheduler,
@ -58,16 +59,18 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
AnalyticsService analyticsService,
PageRepository pageRepository,
PolicyUtils policyUtils,
DatasourceService datasourceService) {
DatasourceService datasourceService,
ConfigService configService) {
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
this.pageRepository = pageRepository;
this.policyUtils = policyUtils;
this.datasourceService = datasourceService;
this.configService = configService;
}
@Override
public Flux<Application> get(MultiValueMap<String, String> params) {
return super.getWithPermission(params, READ_APPLICATIONS);
return setTransientFields(super.getWithPermission(params, READ_APPLICATIONS));
}
@Override
@ -82,32 +85,37 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
@Override
public Mono<Application> findById(String id) {
return repository.findById(id);
return repository.findById(id)
.flatMap(this::setTransientFields);
}
@Override
public Mono<Application> findById(String id, AclPermission aclPermission) {
return repository.findById(id, aclPermission);
return repository.findById(id, aclPermission)
.flatMap(this::setTransientFields);
}
@Override
public Mono<Application> findByIdAndOrganizationId(String id, String organizationId, AclPermission permission) {
return repository.findByIdAndOrganizationId(id, organizationId, permission);
return repository.findByIdAndOrganizationId(id, organizationId, permission)
.flatMap(this::setTransientFields);
}
@Override
public Flux<Application> findByOrganizationId(String organizationId, AclPermission permission) {
return repository.findByOrganizationId(organizationId, permission);
return setTransientFields(repository.findByOrganizationId(organizationId, permission));
}
@Override
public Mono<Application> findByName(String name, AclPermission permission) {
return repository.findByName(name, permission);
return repository.findByName(name, permission)
.flatMap(this::setTransientFields);
}
@Override
public Mono<Application> save(Application application) {
return repository.save(application);
return repository.save(application)
.flatMap(this::setTransientFields);
}
@Override
@ -223,9 +231,9 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
.map(datasource -> {
Datasource updatedDatasource;
if (isPublic) {
updatedDatasource = (Datasource) policyUtils.addPoliciesToExistingObject(datasourcePolicyMap, datasource);
updatedDatasource = policyUtils.addPoliciesToExistingObject(datasourcePolicyMap, datasource);
} else {
updatedDatasource = (Datasource) policyUtils.removePoliciesFromExistingObject(datasourcePolicyMap, datasource);
updatedDatasource = policyUtils.removePoliciesFromExistingObject(datasourcePolicyMap, datasource);
}
return datasourceService.save(updatedDatasource);
@ -243,13 +251,28 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
Application updatedApplication;
if (isPublic) {
updatedApplication = (Application) policyUtils.addPoliciesToExistingObject(applicationPolicyMap, (Application) application);
updatedApplication = policyUtils.addPoliciesToExistingObject(applicationPolicyMap, application);
} else {
updatedApplication = (Application) policyUtils.removePoliciesFromExistingObject(applicationPolicyMap, (Application) application);
updatedApplication = policyUtils.removePoliciesFromExistingObject(applicationPolicyMap, application);
}
return repository.save(updatedApplication);
});
}
}
private Mono<Application> setTransientFields(Application application) {
return setTransientFields(Flux.just(application)).last();
}
private Flux<Application> setTransientFields(Flux<Application> applicationsFlux) {
return configService.getTemplateOrganizationId()
.defaultIfEmpty("")
.cache()
.repeat()
.zipWith(applicationsFlux, (templateOrganizationId, application) -> {
application.setAppIsExample(templateOrganizationId.equals(application.getOrganizationId()));
return application;
});
}
}

View File

@ -7,4 +7,6 @@ public interface ConfigService extends CrudService<Config, String> {
Mono<Config> getByName(String name);
Mono<Config> updateByName(String name, Config config);
Mono<String> getTemplateOrganizationId();
}

View File

@ -18,6 +18,8 @@ import javax.validation.Validator;
@Service
public class ConfigServiceImpl extends BaseService<ConfigRepository, Config, String> implements ConfigService {
private static final String TEMPLATE_ORGANIZATION_CONFIG_NAME = "template-organization";
public ConfigServiceImpl(Scheduler scheduler,
Validator validator,
MongoConverter mongoConverter,
@ -43,4 +45,10 @@ public class ConfigServiceImpl extends BaseService<ConfigRepository, Config, Str
return repository.save(dbConfig);
});
}
@Override
public Mono<String> getTemplateOrganizationId() {
return repository.findByName(TEMPLATE_ORGANIZATION_CONFIG_NAME)
.map(config -> config.getConfig().getAsString(FieldName.ORGANIZATION_ID));
}
}

View File

@ -158,7 +158,7 @@ public class UserOrganizationServiceImpl implements UserOrganizationService {
Map<String, Policy> actionPolicyMap = policyUtils.generateActionPoliciesFromPagePolicies(pagePolicyMap);
//Now update the organization policies
Organization updatedOrganization = (Organization) policyUtils.addPoliciesToExistingObject(orgPolicyMap, organization);
Organization updatedOrganization = policyUtils.addPoliciesToExistingObject(orgPolicyMap, organization);
updatedOrganization.setUserRoles(userRoles);
// Update the underlying application/page/action
@ -221,7 +221,7 @@ public class UserOrganizationServiceImpl implements UserOrganizationService {
Map<String, Policy> actionPolicyMap = policyUtils.generateActionPoliciesFromPagePolicies(pagePolicyMap);
//Now update the organization policies
Organization updatedOrganization = (Organization) policyUtils.removePoliciesFromExistingObject(orgPolicyMap, organization);
Organization updatedOrganization = policyUtils.removePoliciesFromExistingObject(orgPolicyMap, organization);
updatedOrganization.setUserRoles(userRoles);
// Update the underlying application/page/action

View File

@ -325,7 +325,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
Set<AclPermission> invitePermissions = inviteUser.getRole().getPermissions();
// Append the permissions to the application and return
Map<String, Policy> policyMap = policyUtils.generatePolicyFromPermission(invitePermissions, inviteUser);
return (Application) policyUtils.addPoliciesToExistingObject(policyMap, application);
return policyUtils.addPoliciesToExistingObject(policyMap, application);
// Append the required permissions to all the pages
/**

View File

@ -1,23 +1,24 @@
package com.appsmith.server.solutions;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Action;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.Datasource;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.Page;
import com.appsmith.server.domains.User;
import com.appsmith.server.dtos.DslActionDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.repositories.ActionRepository;
import com.appsmith.server.repositories.ApplicationRepository;
import com.appsmith.server.repositories.ConfigRepository;
import com.appsmith.server.repositories.DatasourceRepository;
import com.appsmith.server.repositories.OrganizationRepository;
import com.appsmith.server.repositories.PageRepository;
import com.appsmith.server.services.ActionService;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.DatasourceContextService;
import com.appsmith.server.services.DatasourceService;
import com.appsmith.server.services.OrganizationService;
@ -25,21 +26,23 @@ import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Slf4j
@Component
@RequiredArgsConstructor
public class ExamplesOrganizationCloner {
public static final String TEMPLATE_ORGANIZATION_CONFIG_NAME = "template-organization";
private final OrganizationService organizationService;
private final OrganizationRepository organizationRepository;
private final DatasourceService datasourceService;
@ -48,7 +51,7 @@ public class ExamplesOrganizationCloner {
private final DatasourceRepository datasourceRepository;
private final ApplicationRepository applicationRepository;
private final ActionRepository actionRepository;
private final ConfigRepository configRepository;
private final ConfigService configService;
private final SessionUserService sessionUserService;
private final UserService userService;
private final ApplicationPageService applicationPageService;
@ -64,6 +67,7 @@ public class ExamplesOrganizationCloner {
* Clones the template organization (as specified in config collection) for the given user. The given user will be
* the owner of the cloned organization. This method also assumes that the given user is the same as the user in
* the current Spring session.
*
* @param user User who will be the owner of the cloned organization.
* @return Empty Mono.
*/
@ -73,29 +77,27 @@ public class ExamplesOrganizationCloner {
return Mono.empty();
}
return configRepository.findByName(TEMPLATE_ORGANIZATION_CONFIG_NAME)
return configService.getTemplateOrganizationId()
.doOnSuccess(config -> {
if (config == null) {
// If the template organization could not be found, that's okay, the login should not fail. We
// will try again the next time the user logs in.
log.error(
"Couldn't find config by name {}. Skipping creating example organization for user {}.",
TEMPLATE_ORGANIZATION_CONFIG_NAME,
"Template organization ID not found. Skipping creating example organization for user {}.",
user.getEmail()
);
}
})
.flatMap(config ->
cloneOrganizationForUser(config.getConfig().getAsString(FieldName.ORGANIZATION_ID), user)
);
.flatMap(templateOrganizationId -> cloneOrganizationForUser(templateOrganizationId, user));
}
/**
* Given an organization ID and a user, clone the organization and make the given user the owner of the cloned
* organization. This recursively clones all objects inside the organization. This method also assumes that the
* given user is the same as the user in the current Spring session.
*
* @param templateOrganizationId Organization ID of the organization to create a clone of.
* @param user The user who will own the new cloned organization.
* @param user The user who will own the new cloned organization.
* @return Publishes the newly created organization.
*/
public Mono<Organization> cloneOrganizationForUser(String templateOrganizationId, User user) {
@ -139,12 +141,15 @@ public class ExamplesOrganizationCloner {
/**
* Clone all applications (except deleted ones), including it's pages and actions from one organization into
* another. Also clones all datasources (not just the ones used by any applications) in the given organizations.
*
* @param fromOrganizationId ID of the organization that is the source to copy objects from.
* @param toOrganizationId ID of the organization that is the target to copy objects to.
* @param toOrganizationId ID of the organization that is the target to copy objects to.
* @return Empty Mono.
*/
private Mono<Void> cloneApplications(String fromOrganizationId, String toOrganizationId) {
final Mono<Map<String, Datasource>> cloneDatasourcesMono = cloneDatasources(fromOrganizationId, toOrganizationId).cache();
final List<Page> clonedPages = new ArrayList<>();
return applicationRepository
.findByOrganizationIdAndIsPublicTrue(fromOrganizationId)
.flatMap(application -> {
@ -155,28 +160,27 @@ public class ExamplesOrganizationCloner {
.flatMap(page -> {
final String templatePageId = page.getId();
makePristine(page);
if (page.getLayouts() != null) {
for (final Layout layout : page.getLayouts()) {
layout.setId(new ObjectId().toString());
}
}
return applicationPageService
.createPage(page)
.flatMap(page1 -> {
log.info("Cloned into new page {}", page1);
return applicationRepository.findById(page.getApplicationId())
.map(application -> {
log.info("Application after page got cloned: {}", application);
return page1;
.flatMapMany(savedPage -> {
clonedPages.add(savedPage);
return actionRepository
.findByPageId(templatePageId)
.map(action -> {
log.info("Preparing action for cloning {} {}.", action.getName(), action.getId());
action.setPageId(savedPage.getId());
return action;
});
})
.flatMapMany(
savedPage -> actionRepository
.findByPageId(templatePageId)
.map(action -> {
log.info("Preparing action for cloning {} {}.", action.getName(), action.getId());
action.setPageId(savedPage.getId());
return action;
})
);
});
})
.flatMap(action -> {
log.info("Creating clone of action {}", action.getId());
final String originalActionId = action.getId();
log.info("Creating clone of action {}", originalActionId);
makePristine(action);
action.setOrganizationId(toOrganizationId);
action.setCollectionId(null);
@ -193,7 +197,53 @@ public class ExamplesOrganizationCloner {
datasourceInsideAction.setOrganizationId(toOrganizationId);
}
}
return actionMono.flatMap(actionService::create);
return actionMono
.flatMap(actionService::create)
.map(Action::getId)
.zipWith(Mono.just(originalActionId));
})
// This call to `collectMap` will wait for all actions in all pages to have been processed, and so the
// `clonedPages` list will also contain all pages cloned.
.collectMap(Tuple2::getT2, Tuple2::getT1)
.flatMapMany(actionIdsMap -> {
final List<Mono<Page>> pageSaveMonos = new ArrayList<>();
for (final Page page : clonedPages) {
if (page.getLayouts() == null) {
continue;
}
boolean shouldSave = false;
for (final Layout layout : page.getLayouts()) {
if (layout.getLayoutOnLoadActions() != null) {
for (final Set<DslActionDTO> actionSet : layout.getLayoutOnLoadActions()) {
for (final DslActionDTO actionDTO : actionSet) {
if (actionIdsMap.containsKey(actionDTO.getId())) {
actionDTO.setId(actionIdsMap.get(actionDTO.getId()));
shouldSave = true;
}
}
}
}
if (layout.getPublishedLayoutOnLoadActions() != null) {
for (final Set<DslActionDTO> actionSet : layout.getPublishedLayoutOnLoadActions()) {
for (final DslActionDTO actionDTO : actionSet) {
if (actionIdsMap.containsKey(actionDTO.getId())) {
actionDTO.setId(actionIdsMap.get(actionDTO.getId()));
shouldSave = true;
}
}
}
}
}
if (shouldSave) {
pageSaveMonos.add(pageRepository.save(page));
}
}
return Flux.concat(pageSaveMonos);
})
.then(cloneDatasourcesMono) // Run the datasource cloning mono if it isn't already done.
.then();
@ -217,8 +267,9 @@ public class ExamplesOrganizationCloner {
* Clone all the datasources (except deleted ones) from one organization to another. Publishes a map where the keys
* are IDs of datasources that were copied (source IDs), and the values are the cloned datasource objects which
* contain the new ID.
*
* @param fromOrganizationId ID of the organization that is the source to copy datasources from.
* @param toOrganizationId ID of the organization that is the target to copy datasources to.
* @param toOrganizationId ID of the organization that is the target to copy datasources to.
* @return Mono of a mapping with old datasource IDs as keys and new datasource objects as values.
*/
private Mono<Map<String, Datasource>> cloneDatasources(String fromOrganizationId, String toOrganizationId) {

View File

@ -132,6 +132,7 @@ public class ApplicationServiceTest {
.create(applicationMono)
.assertNext(application -> {
assertThat(application).isNotNull();
assertThat(application.isAppIsExample()).isFalse();
assertThat(application.getId()).isNotNull();
assertThat(application.getName().equals("ApplicationServiceTest TestApp"));
assertThat(application.getPolicies()).isNotEmpty();
@ -201,6 +202,7 @@ public class ApplicationServiceTest {
StepVerifier.create(getApplication)
.assertNext(t -> {
assertThat(t).isNotNull();
assertThat(t.isAppIsExample()).isFalse();
assertThat(t.getId()).isNotNull();
assertThat(t.getName()).isEqualTo("validGetApplicationById-Test");
})
@ -221,6 +223,7 @@ public class ApplicationServiceTest {
StepVerifier.create(getApplication)
.assertNext(t -> {
assertThat(t).isNotNull();
assertThat(t.isAppIsExample()).isFalse();
assertThat(t.getId()).isNotNull();
assertThat(t.getName()).isEqualTo("validGetApplicationByName-Test");
})
@ -248,6 +251,7 @@ public class ApplicationServiceTest {
.filter(t -> t.getName().equals("validGetApplications-Test"))
.forEach(t -> {
assertThat(t.getId()).isNotNull();
assertThat(t.isAppIsExample()).isFalse();
assertThat(t.getPolicies()).isNotEmpty();
assertThat(t.getPolicies()).containsAll(Set.of(readAppPolicy));
});
@ -329,6 +333,7 @@ public class ApplicationServiceTest {
Application application = orgAppDto.getApplications().get(0);
assertThat(application.getUserPermissions()).contains("read:applications");
assertThat(application.isAppIsExample()).isFalse();
})
.verifyComplete();

View File

@ -0,0 +1,102 @@
package com.appsmith.server.solutions;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.ApplicationService;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.OrganizationService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.UserService;
import lombok.extern.slf4j.Slf4j;
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.security.test.context.support.WithUserDetails;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.List;
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
import static org.assertj.core.api.Assertions.assertThat;
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext
public class ExampleApplicationsAreMarked {
@Autowired
UserService userService;
@Autowired
private ApplicationService applicationService;
@Autowired
private OrganizationService organizationService;
@Autowired
private ApplicationPageService applicationPageService;
@Autowired
private SessionUserService sessionUserService;
@MockBean
private ConfigService configService;
@Test
@WithUserDetails(value = "api_user")
public void exampleApplicationsAreMarked() {
Organization newOrganization = new Organization();
newOrganization.setName("Template Organization 3");
final Mono<List<Application>> resultMono = Mono
.zip(
organizationService.create(newOrganization),
sessionUserService.getCurrentUser()
)
.flatMap(tuple -> {
final Organization organization = tuple.getT1();
Mockito.when(configService.getTemplateOrganizationId()).thenReturn(Mono.just(organization.getId()));
final Application app1 = new Application();
app1.setName("first application");
app1.setOrganizationId(organization.getId());
app1.setIsPublic(true);
final Application app2 = new Application();
app2.setName("second application");
app2.setOrganizationId(organization.getId());
app2.setIsPublic(true);
final Application app3 = new Application();
app3.setName("third application");
app3.setOrganizationId(organization.getId());
app3.setIsPublic(false);
return Mono
.when(
applicationPageService.createApplication(app1),
applicationPageService.createApplication(app2),
applicationPageService.createApplication(app3)
)
.thenReturn(organization.getId());
})
.flatMapMany(organizationId -> applicationService.findByOrganizationId(organizationId, READ_APPLICATIONS))
.collectList();
StepVerifier.create(resultMono)
.assertNext(applications -> {
assertThat(applications).hasSize(3);
assertThat(applications.stream().allMatch(Application::isAppIsExample)).isTrue();
})
.verifyComplete();
}
}

View File

@ -1,5 +1,6 @@
package com.appsmith.server.solutions;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.Property;
@ -7,9 +8,11 @@ import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Action;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.Datasource;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.Page;
import com.appsmith.server.domains.Plugin;
import com.appsmith.server.dtos.DslActionDTO;
import com.appsmith.server.helpers.MockPluginExecutor;
import com.appsmith.server.helpers.PluginExecutorHelper;
import com.appsmith.server.repositories.PluginRepository;
@ -19,11 +22,14 @@ import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.ApplicationService;
import com.appsmith.server.services.DatasourceService;
import com.appsmith.server.services.EncryptionService;
import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.services.OrganizationService;
import com.appsmith.server.services.PageService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.UserService;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.bson.types.ObjectId;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -31,6 +37,10 @@ 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.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.http.HttpMethod;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
@ -41,6 +51,7 @@ import reactor.test.StepVerifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@ -96,6 +107,12 @@ public class ExamplesOrganizationClonerTests {
@MockBean
private PluginExecutorHelper pluginExecutorHelper;
@Autowired
private LayoutActionService layoutActionService;
@Autowired
private MongoTemplate mongoTemplate;
private Plugin installedPlugin;
private static class OrganizationData {
@ -485,6 +502,27 @@ public class ExamplesOrganizationClonerTests {
final String pageId1 = app.getPages().get(0).getId();
final Datasource ds1Again = tuple1.getT3();
final Page newPage = new Page();
newPage.setName("A New Page");
newPage.setApplicationId(app.getId());
newPage.setLayouts(new ArrayList<>());
final Layout layout = new Layout();
layout.setId(new ObjectId().toString());
layout.setDsl(new JSONObject(Map.of("text", "draft {{ newPageAction.data }}")));
layout.setPublishedDsl(new JSONObject(Map.of("text", "published {{ newPageAction.data }}")));
final DslActionDTO actionDTO = new DslActionDTO();
final HashSet<DslActionDTO> dslActionDTOS = new HashSet<>(List.of(actionDTO));
layout.setLayoutOnLoadActions(List.of(dslActionDTOS));
newPage.getLayouts().add(layout);
final Action newPageAction = new Action();
newPageAction.setName("newPageAction");
newPageAction.setOrganizationId(organization.getId());
newPageAction.setDatasource(ds1Again);
newPageAction.setPluginId(installedPlugin.getId());
newPageAction.setActionConfiguration(new ActionConfiguration());
newPageAction.getActionConfiguration().setHttpMethod(HttpMethod.GET);
final Action action1 = new Action();
action1.setName("action1");
action1.setPageId(pageId1);
@ -518,6 +556,20 @@ public class ExamplesOrganizationClonerTests {
action4.setPluginId(installedPlugin.getId());
return Mono.when(
applicationPageService.createPage(newPage)
.flatMap(page -> {
newPageAction.setPageId(page.getId());
return applicationPageService.addPageToApplication(app, page, false)
.then(actionCollectionService.createAction(newPageAction))
.flatMap(savedAction -> {
return layoutActionService.updateAction(savedAction.getId(), savedAction);
})
.then(pageService.findById(page.getId(), READ_PAGES));
})
.map(tuple2 -> {
log.info("Created action and added page to app {}", tuple2);
return tuple2;
}),
actionCollectionService.createAction(action1),
actionCollectionService.createAction(action2),
actionCollectionService.createAction(action3),
@ -526,6 +578,9 @@ public class ExamplesOrganizationClonerTests {
})
.then(examplesOrganizationCloner.cloneOrganizationForUser(organization.getId(), tuple.getT2()));
})
.doOnError(error -> {
log.error("Error preparing data for test", error);
})
.flatMap(this::loadOrganizationData);
StepVerifier.create(resultMono)
@ -541,14 +596,21 @@ public class ExamplesOrganizationClonerTests {
"second application"
);
final Application firstApplication = data.applications.stream().filter(app -> app.getName().equals("first application")).findFirst().get();
final Page newPage = mongoTemplate.findOne(Query.query(Criteria.where("applicationId").is(firstApplication.getId()).and("name").is("A New Page")), Page.class);
final String actionId = newPage.getLayouts().get(0).getLayoutOnLoadActions().get(0).iterator().next().getId();
final Action newPageAction = mongoTemplate.findOne(Query.query(Criteria.where("id").is(actionId)), Action.class);
assertThat(newPageAction.getOrganizationId()).isEqualTo(data.organization.getId());
assertThat(data.datasources).hasSize(2);
assertThat(map(data.datasources, Datasource::getName)).containsExactlyInAnyOrder(
"datasource 1",
"datasource 2"
);
assertThat(data.actions).hasSize(4);
assertThat(data.actions).hasSize(5);
assertThat(map(data.actions, Action::getName)).containsExactlyInAnyOrder(
"newPageAction",
"action1",
"action2",
"action3",