Merge branch 'release'

This commit is contained in:
Arpit Mohan 2020-07-28 18:22:04 +05:30
commit ecf07e054e
9 changed files with 728 additions and 55 deletions

View File

@ -121,6 +121,21 @@ jobs:
steps:
# Creating the release on Github
- name: Get the version
id: get_version
run: echo ::set-output name=tag::${GITHUB_REF#refs/tags/}
# If the tag has the string "beta", then mark the Github release as a pre-release
- name: Get the version
id: get_release
run: |
STATUS=false
if [[ ! ${{steps.get_version.outputs.tag}} == *"beta"* ]]; then
STATUS=true
fi
echo ::set-output name=status::${STATUS}
- name: Create Release
id: create_release
uses: actions/create-release@v1
@ -130,5 +145,5 @@ jobs:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
prerelease: ${{steps.get_release.outputs.status}}

View File

@ -24,6 +24,7 @@ public class FieldName {
public static String PROVIDER_ID = "providerId";
public static String CATEGORY = "category";
public static String PAGE = "page";
public static String PAGES = "pages";
public static String SIZE = "size";
public static String ROLE = "role";
public static String DEFAULT_WIDGET_NAME = "MainContainer";

View File

@ -1,5 +1,6 @@
package com.appsmith.server.domains;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@ -9,9 +10,11 @@ import lombok.ToString;
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class ApplicationPage {
String id;
Boolean isDefault;
}

View File

@ -2,6 +2,8 @@ package com.appsmith.server.repositories;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.Page;
import com.mongodb.client.result.UpdateResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -16,4 +18,6 @@ public interface CustomApplicationRepository extends AppsmithRepository<Applicat
Flux<Application> findByOrganizationId(String orgId, AclPermission permission);
Flux<Application> findByMultipleOrganizationIds(Set<String> orgIds, AclPermission permission);
Mono<UpdateResult> addPageToApplication(Application application, Page page, boolean isDefault);
}

View File

@ -2,14 +2,20 @@ package com.appsmith.server.repositories;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.acl.PolicyGenerator;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationPage;
import com.appsmith.server.domains.Page;
import com.appsmith.server.domains.QApplication;
import com.mongodb.client.result.UpdateResult;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -64,4 +70,14 @@ public class CustomApplicationRepositoryImpl extends BaseAppsmithRepositoryImpl<
return queryAll(List.of(orgIdsCriteria), permission);
}
@Override
public Mono<UpdateResult> addPageToApplication(Application application, Page page, boolean isDefault) {
final ApplicationPage applicationPage = new ApplicationPage(page.getId(), isDefault);
return mongoOperations.updateFirst(
Query.query(getIdCriteria(application.getId())),
new Update().addToSet(FieldName.PAGES, applicationPage),
Application.class
);
}
}

View File

@ -2,12 +2,13 @@ package com.appsmith.server.services;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.Page;
import com.mongodb.client.result.UpdateResult;
import reactor.core.publisher.Mono;
public interface ApplicationPageService {
Mono<Page> createPage(Page page);
Mono<Application> addPageToApplication(Mono<Application> applicationMono, Page page, Boolean isDefault);
Mono<UpdateResult> addPageToApplication(Application application, Page page, Boolean isDefault);
Mono<Page> getPage(String pageId, Boolean viewMode);
@ -19,5 +20,7 @@ public interface ApplicationPageService {
Mono<Application> makePageDefault(String applicationId, String pageId);
Mono<Application> cloneApplication(Application application);
Mono<Application> deleteApplication(String id);
}

View File

@ -13,11 +13,15 @@ import com.appsmith.server.domains.Page;
import com.appsmith.server.domains.User;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.repositories.ApplicationRepository;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@ -40,18 +44,22 @@ public class ApplicationPageServiceImpl implements ApplicationPageService {
private final AnalyticsService analyticsService;
private final PolicyGenerator policyGenerator;
private final ApplicationRepository applicationRepository;
public ApplicationPageServiceImpl(ApplicationService applicationService,
PageService pageService,
SessionUserService sessionUserService,
OrganizationService organizationService,
AnalyticsService analyticsService,
PolicyGenerator policyGenerator) {
PolicyGenerator policyGenerator,
ApplicationRepository applicationRepository) {
this.applicationService = applicationService;
this.pageService = pageService;
this.sessionUserService = sessionUserService;
this.organizationService = organizationService;
this.analyticsService = analyticsService;
this.policyGenerator = policyGenerator;
this.applicationRepository = applicationRepository;
}
public Mono<Page> createPage(Page page) {
@ -87,34 +95,31 @@ public class ApplicationPageServiceImpl implements ApplicationPageService {
return pageMono
.flatMap(pageService::createDefault)
//After the page has been saved, update the application (save the page id inside the application)
.flatMap(savedPage ->
addPageToApplication(applicationMono, savedPage, false)
.thenReturn(savedPage));
.zipWith(applicationMono)
.flatMap(tuple -> {
final Page savedPage = tuple.getT1();
final Application application = tuple.getT2();
return addPageToApplication(application, savedPage, false)
.thenReturn(savedPage);
});
}
/**
* This function is called during page create in Page Service. It adds the newly created
* page to its ApplicationPages list.
* This function is called during page create in Page Service. It adds the given page to its ApplicationPages list.
* Note: It is assumed here that `application` is already checked for the MANAGE_APPLICATIONS policy.
*
* @param applicationMono
* @param page
* @return Updated application
* @param application Application to which the page will be added. Should have an `id` already.
* @param page Page to be added to the application. Should have an `id` already.
* @return UpdateResult object with details on how many documents have been updated, which should be 0 or 1.
*/
public Mono<Application> addPageToApplication(Mono<Application> applicationMono, Page page, Boolean isDefault) {
return applicationMono
.map(application -> {
List<ApplicationPage> applicationPages = application.getPages();
if (applicationPages == null) {
applicationPages = new ArrayList<>();
@Override
public Mono<UpdateResult> addPageToApplication(Application application, Page page, Boolean isDefault) {
return applicationRepository.addPageToApplication(application, page, isDefault)
.doOnSuccess(result -> {
if (result.getModifiedCount() != 1) {
log.error("Add page to application didn't update anything, probably because application wasn't found.");
}
ApplicationPage applicationPage = new ApplicationPage();
applicationPage.setId(page.getId());
applicationPage.setIsDefault(isDefault);
applicationPages.add(applicationPage);
application.setPages(applicationPages);
return application;
})
.flatMap(applicationService::save);
});
}
public Mono<Page> getPage(String pageId, Boolean viewMode) {
@ -246,10 +251,53 @@ public class ApplicationPageServiceImpl implements ApplicationPageService {
return pageService
.createDefault(page)
.flatMap(savedPage -> addPageToApplication(Mono.just(savedApplication), savedPage, true));
.flatMap(savedPage -> addPageToApplication(savedApplication, savedPage, true))
.then(applicationService.findById(savedApplication.getId(), READ_APPLICATIONS));
});
}
@Override
public Mono<Application> cloneApplication(Application application) {
if (!StringUtils.hasText(application.getName())) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.NAME));
}
String orgId = application.getOrganizationId();
if (!StringUtils.hasText(orgId)) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ORGANIZATION_ID));
}
// Clean the object so that it will be saved as a new application for the currently signed in user.
application.setId(null);
application.setPolicies(new HashSet<>());
application.setPages(new ArrayList<>());
Mono<User> userMono = sessionUserService.getCurrentUser().cache();
Mono<Application> applicationWithPoliciesMono = userMono
.flatMap(user -> {
Mono<Organization> orgMono = organizationService.findById(orgId, ORGANIZATION_MANAGE_APPLICATIONS)
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ORGANIZATION, orgId)));
return orgMono.map(org -> {
application.setOrganizationId(org.getId());
// At the organization level, filter out all the application specific policies and apply them
// to the new application that we are creating.
Set<Policy> policySet = org.getPolicies().stream()
.filter(policy ->
policy.getPermission().equals(ORGANIZATION_MANAGE_APPLICATIONS.getValue()) ||
policy.getPermission().equals(ORGANIZATION_READ_APPLICATIONS.getValue())
).collect(Collectors.toSet());
Set<Policy> documentPolicies = policyGenerator.getAllChildPolicies(policySet, Organization.class, Application.class);
application.setPolicies(documentPolicies);
return application;
});
});
return applicationWithPoliciesMono
.flatMap(applicationService::createDefault);
}
private void generateAndSetPagePolicies(Application application, User user, Page page) {
Set<Policy> policySet = application.getPolicies().stream()
.filter(policy -> policy.getPermission().equals(MANAGE_APPLICATIONS.getValue())

View File

@ -3,8 +3,10 @@ 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.Organization;
import com.appsmith.server.domains.Page;
import com.appsmith.server.domains.User;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
@ -94,7 +96,8 @@ public class ExamplesOrganizationCloner {
* @param user The user who will own the new cloned organization.
* @return Publishes the newly created organization.
*/
private Mono<Organization> cloneOrganizationForUser(String templateOrganizationId, User user) {
public Mono<Organization> cloneOrganizationForUser(String templateOrganizationId, User user) {
log.info("Cloning organization id {}", templateOrganizationId);
return organizationRepository
.findById(templateOrganizationId)
.doOnSuccess(organization -> {
@ -144,46 +147,70 @@ public class ExamplesOrganizationCloner {
.findByOrganizationIdAndIsPublicTrue(fromOrganizationId)
.flatMap(application -> {
final String templateApplicationId = application.getId();
makePristine(application);
application.setOrganizationId(toOrganizationId);
if (!CollectionUtils.isEmpty(application.getPages())) {
application.getPages().clear();
}
return Flux.combineLatest(
pageRepository.findByApplicationId(templateApplicationId),
applicationPageService.createApplication(application).cache(),
(page, savedApplication) -> {
log.info("Cloned application {} into new application {}", templateApplicationId, savedApplication.getId());
page.setApplicationId(savedApplication.getId());
return page;
}
);
return doCloneApplication(application, templateApplicationId);
})
.flatMap(page -> {
final String templatePageId = page.getId();
makePristine(page);
return Flux.combineLatest(
actionRepository.findByPageId(templatePageId),
applicationPageService.createPage(page).cache(),
(action, savedPage) -> {
action.setPageId(savedPage.getId());
return action;
}
);
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 -> actionRepository
.findByPageId(templatePageId)
.map(action -> {
log.info("Preparing action for cloning {} {}.", action.getName(), action.getId());
action.setPageId(savedPage.getId());
return action;
})
);
})
.zipWith(cloneDatasourcesMono)
.flatMap(tuple -> {
final Action action = tuple.getT1();
final Map<String, Datasource> newDatasourcesByTemplateId = tuple.getT2();
.flatMap(action -> {
log.info("Creating clone of action {}", action.getId());
makePristine(action);
action.setOrganizationId(toOrganizationId);
action.setCollectionId(null);
action.setDatasource(newDatasourcesByTemplateId.get(action.getDatasource().getId()));
return actionService.create(action);
Mono<Action> actionMono = Mono.just(action);
final Datasource datasourceInsideAction = action.getDatasource();
if (datasourceInsideAction != null) {
if (datasourceInsideAction.getId() != null) {
actionMono = cloneDatasourcesMono
.map(newDatasourcesByTemplateId -> {
action.setDatasource(newDatasourcesByTemplateId.get(datasourceInsideAction.getId()));
return action;
});
} else {
datasourceInsideAction.setOrganizationId(toOrganizationId);
}
}
return actionMono.flatMap(actionService::create);
})
.then(cloneDatasourcesMono) // Run the datasource cloning mono if it isn't already done.
.then();
}
private Flux<Page> doCloneApplication(Application application, String templateApplicationId) {
return applicationPageService
.cloneApplication(application)
.flatMapMany(
savedApplication -> pageRepository
.findByApplicationId(templateApplicationId)
.map(page -> {
log.info("Preparing page for cloning {} {}.", page.getName(), page.getId());
page.setApplicationId(savedApplication.getId());
return page;
})
);
}
/**
* 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
@ -202,7 +229,7 @@ public class ExamplesOrganizationCloner {
}
makePristine(datasource);
datasource.setOrganizationId(toOrganizationId);
datasource.setName(datasource.getName() + " cloned " + Math.random());
datasource.setName(datasource.getName());
return Mono.zip(
Mono.just(templateDatasourceId),
datasourceService.create(datasource)

View File

@ -0,0 +1,556 @@
package com.appsmith.server.solutions;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.Property;
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.Organization;
import com.appsmith.server.domains.Page;
import com.appsmith.server.domains.Plugin;
import com.appsmith.server.helpers.MockPluginExecutor;
import com.appsmith.server.helpers.PluginExecutorHelper;
import com.appsmith.server.repositories.PluginRepository;
import com.appsmith.server.services.ActionCollectionService;
import com.appsmith.server.services.ActionService;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.ApplicationService;
import com.appsmith.server.services.DatasourceService;
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 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.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.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.READ_DATASOURCES;
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 ExamplesOrganizationClonerTests {
@Autowired
UserService userService;
@Autowired
private ExamplesOrganizationCloner examplesOrganizationCloner;
@Autowired
private ApplicationService applicationService;
@Autowired
private DatasourceService datasourceService;
@Autowired
private OrganizationService organizationService;
@Autowired
private ApplicationPageService applicationPageService;
@Autowired
private SessionUserService sessionUserService;
@Autowired
private ActionService actionService;
@Autowired
private PageService pageService;
@Autowired
private ActionCollectionService actionCollectionService;
@Autowired
private PluginRepository pluginRepository;
@MockBean
private PluginExecutorHelper pluginExecutorHelper;
private Plugin installedPlugin;
private static class OrganizationData {
Organization organization;
List<Application> applications = new ArrayList<>();
List<Datasource> datasources = new ArrayList<>();
List<Action> actions = new ArrayList<>();
}
public Mono<OrganizationData> loadOrganizationData(Organization organization) {
final OrganizationData data = new OrganizationData();
data.organization = organization;
return Mono
.when(
applicationService
.findByOrganizationId(organization.getId(), READ_APPLICATIONS)
.map(data.applications::add),
datasourceService
.findAllByOrganizationId(organization.getId(), READ_DATASOURCES)
.map(data.datasources::add),
getActionsInOrganization(organization)
.map(data.actions::add)
)
.thenReturn(data);
}
@Before
public void setup() {
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor()));
installedPlugin = pluginRepository.findByPackageName("installed-plugin").block();
}
@Test
@WithUserDetails(value = "api_user")
public void cloneEmptyOrganization() {
Organization newOrganization = new Organization();
newOrganization.setName("Template Organization");
final Mono<OrganizationData> resultMono = organizationService.create(newOrganization)
.zipWith(sessionUserService.getCurrentUser())
.flatMap(tuple ->
examplesOrganizationCloner.cloneOrganizationForUser(tuple.getT1().getId(), tuple.getT2()))
.flatMap(this::loadOrganizationData);
StepVerifier.create(resultMono)
.assertNext(data -> {
assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples");
assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).isEmpty();
assertThat(data.datasources).isEmpty();
assertThat(data.actions).isEmpty();
})
.verifyComplete();
}
@Test
@WithUserDetails(value = "api_user")
public void cloneOrganizationWithItsContents() {
Organization newOrganization = new Organization();
newOrganization.setName("Template Organization");
final Mono<OrganizationData> resultMono = Mono
.zip(
organizationService.create(newOrganization),
sessionUserService.getCurrentUser()
)
.flatMap(tuple -> {
final Organization organization = tuple.getT1();
Application app1 = new Application();
app1.setName("1 - public app");
app1.setOrganizationId(organization.getId());
app1.setIsPublic(true);
Application app2 = new Application();
app2.setOrganizationId(organization.getId());
app2.setName("2 - private app");
return Mono.when(
applicationPageService.createApplication(app1),
applicationPageService.createApplication(app2)
).then(examplesOrganizationCloner.cloneOrganizationForUser(organization.getId(), tuple.getT2()));
})
.flatMap(this::loadOrganizationData);
StepVerifier.create(resultMono)
.assertNext(data -> {
assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples");
assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).hasSize(1);
assertThat(map(data.applications, Application::getName)).containsExactly("1 - public app");
assertThat(data.applications.get(0).getPages()).hasSize(1);
assertThat(data.datasources).isEmpty();
assertThat(data.actions).isEmpty();
})
.verifyComplete();
}
@Test
@WithUserDetails(value = "api_user")
public void cloneOrganizationWithOnlyPublicApplications() {
Organization newOrganization = new Organization();
newOrganization.setName("Template Organization 2");
final Mono<OrganizationData> resultMono = Mono
.zip(
organizationService.create(newOrganization),
sessionUserService.getCurrentUser()
)
.flatMap(tuple -> {
final Organization organization = tuple.getT1();
Application app1 = new Application();
app1.setName("1 - public app more");
app1.setOrganizationId(organization.getId());
app1.setIsPublic(true);
Application app2 = new Application();
app2.setOrganizationId(organization.getId());
app2.setName("2 - another public app more");
app2.setIsPublic(true);
return Mono.zip(
applicationPageService.createApplication(app1),
applicationPageService.createApplication(app2).flatMap(application -> {
final Page newPage = new Page();
newPage.setName("The New Page");
newPage.setApplicationId(application.getId());
return applicationPageService.createPage(newPage);
})
).then(examplesOrganizationCloner.cloneOrganizationForUser(organization.getId(), tuple.getT2()));
})
.flatMap(this::loadOrganizationData);
StepVerifier.create(resultMono)
.assertNext(data -> {
assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples");
assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).hasSize(2);
assertThat(map(data.applications, Application::getName)).containsExactlyInAnyOrder(
"1 - public app more",
"2 - another public app more"
);
for (final Application app : data.applications) {
if ("2 - another public app more".equals(app.getName())) {
assertThat(app.getPages()).hasSize(2);
} else {
assertThat(app.getPages()).hasSize(1);
}
}
assertThat(data.datasources).isEmpty();
assertThat(data.actions).isEmpty();
})
.verifyComplete();
}
@Test
@WithUserDetails(value = "api_user")
public void cloneOrganizationWithOnlyPrivateApplications() {
Organization newOrganization = new Organization();
newOrganization.setName("Template Organization 2");
final Mono<OrganizationData> resultMono = Mono
.zip(
organizationService.create(newOrganization),
sessionUserService.getCurrentUser()
)
.flatMap(tuple -> {
final Organization organization = tuple.getT1();
Application app1 = new Application();
app1.setName("1 - private app more");
app1.setOrganizationId(organization.getId());
Application app2 = new Application();
app2.setOrganizationId(organization.getId());
app2.setName("2 - another private app more");
return Mono.when(
applicationPageService.createApplication(app1),
applicationPageService.createApplication(app2)
).then(examplesOrganizationCloner.cloneOrganizationForUser(organization.getId(), tuple.getT2()));
})
.flatMap(this::loadOrganizationData);
StepVerifier.create(resultMono)
.assertNext(data -> {
assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples");
assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).isEmpty();
assertThat(data.datasources).isEmpty();
assertThat(data.actions).isEmpty();
})
.verifyComplete();
}
@Test
@WithUserDetails(value = "api_user")
public void cloneOrganizationWithOnlyDatasources() {
Organization newOrganization = new Organization();
newOrganization.setName("Template Organization 2");
final Mono<OrganizationData> resultMono = Mono
.zip(
organizationService.create(newOrganization),
sessionUserService.getCurrentUser()
)
.flatMap(tuple -> {
final Organization organization = tuple.getT1();
final Datasource ds1 = new Datasource();
ds1.setName("datasource 1");
ds1.setOrganizationId(organization.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("datasource 2");
ds2.setOrganizationId(organization.getId());
return Mono.when(
datasourceService.create(ds1),
datasourceService.create(ds2)
).then(examplesOrganizationCloner.cloneOrganizationForUser(organization.getId(), tuple.getT2()));
})
.flatMap(this::loadOrganizationData);
StepVerifier.create(resultMono)
.assertNext(data -> {
assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples");
assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.datasources).hasSize(2);
assertThat(map(data.datasources, Datasource::getName)).containsExactlyInAnyOrder(
"datasource 1",
"datasource 2"
);
final Datasource ds1 = data.datasources.stream()
.filter(datasource -> "datasource 1".equals(datasource.getName()))
.findFirst()
.orElseThrow();
assertThat(ds1.getDatasourceConfiguration().getUrl()).isEqualTo("http://httpbin.org/get");
assertThat(ds1.getDatasourceConfiguration().getHeaders()).containsOnly(
new Property("X-Answer", "42")
);
assertThat(data.applications).isEmpty();
assertThat(data.actions).isEmpty();
})
.verifyComplete();
}
@Test
@WithUserDetails(value = "api_user")
public void cloneOrganizationWithDatasourcesAndApplications() {
Organization newOrganization = new Organization();
newOrganization.setName("Template Organization 2");
final Mono<OrganizationData> resultMono = Mono
.zip(
organizationService.create(newOrganization),
sessionUserService.getCurrentUser()
)
.flatMap(tuple -> {
final Organization organization = tuple.getT1();
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 Datasource ds1 = new Datasource();
ds1.setName("datasource 1");
ds1.setOrganizationId(organization.getId());
final Datasource ds2 = new Datasource();
ds2.setName("datasource 2");
ds2.setOrganizationId(organization.getId());
return Mono.when(
applicationPageService.createApplication(app1),
applicationPageService.createApplication(app2),
datasourceService.create(ds1),
datasourceService.create(ds2)
).then(examplesOrganizationCloner.cloneOrganizationForUser(organization.getId(), tuple.getT2()));
})
.flatMap(this::loadOrganizationData);
StepVerifier.create(resultMono)
.assertNext(data -> {
assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples");
assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).hasSize(2);
assertThat(map(data.applications, Application::getName)).containsExactlyInAnyOrder(
"first application",
"second application"
);
assertThat(data.datasources).hasSize(2);
assertThat(map(data.datasources, Datasource::getName)).containsExactlyInAnyOrder(
"datasource 1",
"datasource 2"
);
assertThat(data.actions).isEmpty();
})
.verifyComplete();
}
@Test
@WithUserDetails(value = "api_user")
public void cloneOrganizationWithDatasourcesAndApplicationsAndActions() {
Organization newOrganization = new Organization();
newOrganization.setName("Template Organization 2");
final Mono<OrganizationData> resultMono = Mono
.zip(
organizationService.create(newOrganization),
sessionUserService.getCurrentUser()
)
.flatMap(tuple -> {
final Organization organization = tuple.getT1();
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 Datasource ds1 = new Datasource();
ds1.setName("datasource 1");
ds1.setOrganizationId(organization.getId());
ds1.setPluginId(installedPlugin.getId());
final Datasource ds2 = new Datasource();
ds2.setName("datasource 2");
ds2.setOrganizationId(organization.getId());
ds2.setPluginId(installedPlugin.getId());
return Mono
.zip(
applicationPageService.createApplication(app1),
applicationPageService.createApplication(app2),
datasourceService.create(ds1),
datasourceService.create(ds2)
)
.flatMap(tuple1 -> {
final Application app = tuple1.getT1();
final String pageId1 = app.getPages().get(0).getId();
final Datasource ds1Again = tuple1.getT3();
final Action action1 = new Action();
action1.setName("action1");
action1.setPageId(pageId1);
action1.setOrganizationId(organization.getId());
action1.setDatasource(ds1Again);
action1.setPluginId(installedPlugin.getId());
final Action action2 = new Action();
action2.setPageId(pageId1);
action2.setName("action2");
action2.setOrganizationId(organization.getId());
action2.setDatasource(ds1Again);
action2.setPluginId(installedPlugin.getId());
final Application app2Again = tuple1.getT2();
final String pageId2 = app2Again.getPages().get(0).getId();
final Datasource ds2Again = tuple1.getT4();
final Action action3 = new Action();
action3.setName("action3");
action3.setPageId(pageId2);
action3.setOrganizationId(organization.getId());
action3.setDatasource(ds2Again);
action3.setPluginId(installedPlugin.getId());
final Action action4 = new Action();
action4.setPageId(pageId2);
action4.setName("action4");
action4.setOrganizationId(organization.getId());
action4.setDatasource(ds2Again);
action4.setPluginId(installedPlugin.getId());
return Mono.when(
actionCollectionService.createAction(action1),
actionCollectionService.createAction(action2),
actionCollectionService.createAction(action3),
actionCollectionService.createAction(action4)
);
})
.then(examplesOrganizationCloner.cloneOrganizationForUser(organization.getId(), tuple.getT2()));
})
.flatMap(this::loadOrganizationData);
StepVerifier.create(resultMono)
.assertNext(data -> {
assertThat(data.organization).isNotNull();
assertThat(data.organization.getId()).isNotNull();
assertThat(data.organization.getName()).isEqualTo("api_user's Examples");
assertThat(data.organization.getPolicies()).isNotEmpty();
assertThat(data.applications).hasSize(2);
assertThat(map(data.applications, Application::getName)).containsExactlyInAnyOrder(
"first application",
"second application"
);
assertThat(data.datasources).hasSize(2);
assertThat(map(data.datasources, Datasource::getName)).containsExactlyInAnyOrder(
"datasource 1",
"datasource 2"
);
assertThat(data.actions).hasSize(4);
assertThat(map(data.actions, Action::getName)).containsExactlyInAnyOrder(
"action1",
"action2",
"action3",
"action4"
);
})
.verifyComplete();
}
private <InType, OutType> List<OutType> map(List<InType> list, Function<InType, OutType> fn) {
return list.stream().map(fn).collect(Collectors.toList());
}
private Flux<Action> getActionsInOrganization(Organization organization) {
return applicationService
.findByOrganizationId(organization.getId(), READ_APPLICATIONS)
.flatMap(application -> pageService.findByApplicationId(application.getId(), READ_PAGES))
.flatMap(page -> actionService.get(new LinkedMultiValueMap<String, String>(
Map.of(FieldName.PAGE_ID, Collections.singletonList(page.getId())))));
}
}