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/services/ApplicationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java index c8c3867ab5..8f36d1075e 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 @@ -252,4 +260,19 @@ 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/solutions/ExamplesOrganizationCloner.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java index 4cfb2efaff..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,7 +1,6 @@ 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; @@ -14,12 +13,12 @@ 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; @@ -44,8 +43,6 @@ import java.util.Set; @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; @@ -54,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; @@ -80,21 +77,18 @@ 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)); } /** 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(); + } + +}