diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java index f27cc25b81..ad9beda0dc 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java @@ -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 -> { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java index 5ffdca8b97..13ef1b84e6 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java @@ -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 pages; + + @Transient + boolean appIsExample = false; + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java index e34fb87c01..0658fd185a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java @@ -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 { +public class PolicyUtils { private final PolicyGenerator policyGenerator; private final ApplicationRepository applicationRepository; @@ -55,7 +55,7 @@ public class PolicyUtils { this.datasourceRepository = datasourceRepository; } - public T addPoliciesToExistingObject(Map policyMap, T obj) { + public T addPoliciesToExistingObject(Map 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 policyMap1 = new HashMap<>(); @@ -83,7 +83,7 @@ public class PolicyUtils { return obj; } - public T removePoliciesFromExistingObject(Map policyMap, T obj) { + public T removePoliciesFromExistingObject(Map 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 policyMap1 = new HashMap<>(); @@ -168,9 +168,9 @@ public class PolicyUtils { .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 { .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 { .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 { .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 -> { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ActionRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ActionRepository.java index a50e89dded..a1e09091bc 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ActionRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ActionRepository.java @@ -19,4 +19,6 @@ public interface ActionRepository extends BaseRepository, Custom Mono countByDatasourceId(String datasourceId); Flux findByPageId(String pageId); + + Flux findByOrganizationId(String organizationId); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java index c8c3867ab5..278c43e5d2 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java @@ -48,6 +48,7 @@ public class ApplicationServiceImpl extends BaseService get(MultiValueMap params) { - return super.getWithPermission(params, READ_APPLICATIONS); + return setTransientFields(super.getWithPermission(params, READ_APPLICATIONS)); } @Override @@ -82,32 +85,37 @@ public class ApplicationServiceImpl extends BaseService findById(String id) { - return repository.findById(id); + return repository.findById(id) + .flatMap(this::setTransientFields); } @Override public Mono findById(String id, AclPermission aclPermission) { - return repository.findById(id, aclPermission); + return repository.findById(id, aclPermission) + .flatMap(this::setTransientFields); } @Override public Mono 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 findByOrganizationId(String organizationId, AclPermission permission) { - return repository.findByOrganizationId(organizationId, permission); + return setTransientFields(repository.findByOrganizationId(organizationId, permission)); } @Override public Mono findByName(String name, AclPermission permission) { - return repository.findByName(name, permission); + return repository.findByName(name, permission) + .flatMap(this::setTransientFields); } @Override public Mono save(Application application) { - return repository.save(application); + return repository.save(application) + .flatMap(this::setTransientFields); } @Override @@ -223,9 +231,9 @@ public class ApplicationServiceImpl extends BaseService { 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 setTransientFields(Application application) { + return setTransientFields(Flux.just(application)).last(); + } + + private Flux setTransientFields(Flux applicationsFlux) { + return configService.getTemplateOrganizationId() + .defaultIfEmpty("") + .cache() + .repeat() + .zipWith(applicationsFlux, (templateOrganizationId, application) -> { + application.setAppIsExample(templateOrganizationId.equals(application.getOrganizationId())); + return application; + }); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConfigService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConfigService.java index c930844130..4eedd63166 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConfigService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConfigService.java @@ -7,4 +7,6 @@ public interface ConfigService extends CrudService { Mono getByName(String name); Mono updateByName(String name, Config config); + + Mono getTemplateOrganizationId(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConfigServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConfigServiceImpl.java index 88163b2dd1..9e55cc61eb 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConfigServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConfigServiceImpl.java @@ -18,6 +18,8 @@ import javax.validation.Validator; @Service public class ConfigServiceImpl extends BaseService 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 getTemplateOrganizationId() { + return repository.findByName(TEMPLATE_ORGANIZATION_CONFIG_NAME) + .map(config -> config.getConfig().getAsString(FieldName.ORGANIZATION_ID)); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java index d20fe49092..24c6fa7978 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java @@ -158,7 +158,7 @@ public class UserOrganizationServiceImpl implements UserOrganizationService { Map 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 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 diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java index 63b80fea8b..fec743578f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java @@ -325,7 +325,7 @@ public class UserServiceImpl extends BaseService i Set invitePermissions = inviteUser.getRole().getPermissions(); // Append the permissions to the application and return Map policyMap = policyUtils.generatePolicyFromPermission(invitePermissions, inviteUser); - return (Application) policyUtils.addPoliciesToExistingObject(policyMap, application); + return policyUtils.addPoliciesToExistingObject(policyMap, application); // Append the required permissions to all the pages /** diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java index 52bccd314c..3515cb9e96 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java @@ -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 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 cloneApplications(String fromOrganizationId, String toOrganizationId) { final Mono> cloneDatasourcesMono = cloneDatasources(fromOrganizationId, toOrganizationId).cache(); + final List 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> 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 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 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> cloneDatasources(String fromOrganizationId, String toOrganizationId) { diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java index 435a1b9e19..4b00029e4a 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java @@ -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(); diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExampleApplicationsAreMarked.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExampleApplicationsAreMarked.java new file mode 100644 index 0000000000..84205cebe7 --- /dev/null +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExampleApplicationsAreMarked.java @@ -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> 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(); + } + +} diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java index 808a8ffad4..8417650018 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java @@ -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 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",