Merging community release
This commit is contained in:
commit
9c0b2a4e33
4
.github/workflows/client.yml
vendored
4
.github/workflows/client.yml
vendored
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
env:
|
||||
cache-name: cache-yarn-dependencies
|
||||
with:
|
||||
# maven dependencies are stored in `~/.m2` on Linux/macOS
|
||||
# npm dependencies are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
|
|
@ -229,6 +229,6 @@ jobs:
|
|||
if: success() && github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
docker build -t appsmith/appsmith-editor-ee:${GITHUB_SHA} .
|
||||
docker build -t appsmith/appsmith-editor-ee:latest .
|
||||
docker build -t appsmith/appsmith-editor-ee:nightly .
|
||||
echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin
|
||||
docker push appsmith/appsmith-editor-ee
|
||||
149
.github/workflows/github-release.yml
vendored
Normal file
149
.github/workflows/github-release.yml
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
name: Appsmith Github Release Workflow
|
||||
|
||||
on:
|
||||
push:
|
||||
# Only trigger if a tag has been created and pushed to this branch
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build-client:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: app/client
|
||||
|
||||
steps:
|
||||
# Checkout the code
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 10.16.3
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '10.16.3'
|
||||
|
||||
# Retrieve npm dependencies from cache. After a successful run, these dependencies are cached again
|
||||
- name: Cache npm dependencies
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-yarn-dependencies
|
||||
with:
|
||||
# npm dependencies are stored in `~/.m2` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.OS }}-node-
|
||||
${{ runner.OS }}-
|
||||
|
||||
# Install all the dependencies
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Set the build environment based on the branch
|
||||
id: vars
|
||||
run: |
|
||||
REACT_APP_ENVIRONMENT="PRODUCTION"
|
||||
echo ::set-output name=REACT_APP_ENVIRONMENT::${REACT_APP_ENVIRONMENT}
|
||||
|
||||
- name: Create the bundle
|
||||
run: REACT_APP_ENVIRONMENT=${{steps.vars.outputs.REACT_APP_ENVIRONMENT}} yarn build
|
||||
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/tags/}
|
||||
|
||||
# Build Docker image and push to Docker Hub
|
||||
- name: Push production image to Docker Hub with commit tag
|
||||
run: |
|
||||
docker build -t appsmith/appsmith-editor:${{steps.get_version.outputs.tag}} .
|
||||
|
||||
# Only build & tag with latest if the tag doesn't contain beta
|
||||
if [[ ! ${{steps.get_version.outputs.tag}} == *"beta"* ]]; then
|
||||
docker build -t appsmith/appsmith-editor:latest .
|
||||
fi
|
||||
|
||||
echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin
|
||||
docker push appsmith/appsmith-editor
|
||||
|
||||
build-server:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: app/server
|
||||
|
||||
steps:
|
||||
# Checkout the code
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# Setup Java
|
||||
- name: Set up JDK 1.11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.11
|
||||
|
||||
# Retrieve maven dependencies from cache. After a successful run, these dependencies are cached again
|
||||
- name: Cache maven dependencies
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-maven-dependencies
|
||||
with:
|
||||
# maven dependencies are stored in `~/.m2` on Linux/macOS
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-m2
|
||||
|
||||
# Build the code
|
||||
- name: Build without running any tests
|
||||
run: mvn -B package -DskipTests
|
||||
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/tags/}
|
||||
|
||||
# Build Docker image and push to Docker Hub
|
||||
- name: Push image to Docker Hub
|
||||
run: |
|
||||
docker build -t appsmith/appsmith-server:${{steps.get_version.outputs.tag}} .
|
||||
|
||||
# Only build & tag with latest if the tag doesn't contain beta
|
||||
if [[ ! ${{steps.get_version.outputs.tag}} == *"beta"* ]]; then
|
||||
docker build -t appsmith/appsmith-server:latest .
|
||||
fi
|
||||
|
||||
echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin
|
||||
docker push appsmith/appsmith-server
|
||||
|
||||
create-release:
|
||||
needs:
|
||||
- build-server
|
||||
- build-client
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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_prerelease
|
||||
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
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: ${{steps.get_prerelease.outputs.status}}
|
||||
|
||||
2
.github/workflows/server.yml
vendored
2
.github/workflows/server.yml
vendored
|
|
@ -79,6 +79,6 @@ jobs:
|
|||
if: success() && github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
docker build -t appsmith/appsmith-server-ee:${GITHUB_SHA} .
|
||||
docker build -t appsmith/appsmith-server-ee:latest .
|
||||
docker build -t appsmith/appsmith-server-ee:nightly .
|
||||
echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin
|
||||
docker push appsmith/appsmith-server-ee
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ describe("Test curl import flow", function() {
|
|||
cy.ResponseStatusCheck("200 OK");
|
||||
cy.get(ApiEditor.formActionButtons).should("be.visible");
|
||||
cy.get(ApiEditor.ApiDeleteBtn).click();
|
||||
cy.get(ApiEditor.ApiDeleteBtn).should("be.disabled");
|
||||
cy.wait("@deleteAction");
|
||||
cy.get("@deleteAction").then(response => {
|
||||
cy.expect(response.response.body.responseMeta.success).to.eq(true);
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.CreateAPI(apiname);
|
||||
cy.log("Creation of API Action successful");
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
|
||||
cy.WaitAutoSave();
|
||||
cy.RunAPI();
|
||||
cy.ResponseStatusCheck(testdata.successStatusCode);
|
||||
cy.log("Response code check successful");
|
||||
|
|
@ -135,6 +136,7 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.CreateAPI("ThirdAPI");
|
||||
cy.log("Creation of API Action successful");
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.queryAndValue);
|
||||
cy.WaitAutoSave();
|
||||
cy.RunAPI();
|
||||
cy.ResponseStatusCheck("200 OK");
|
||||
cy.log("Response code check successful");
|
||||
|
|
@ -153,6 +155,7 @@ describe("API Panel Test Functionality", function() {
|
|||
testdata.queryKey,
|
||||
testdata.queryValue,
|
||||
);
|
||||
cy.WaitAutoSave();
|
||||
cy.RunAPI();
|
||||
cy.ResponseStatusCheck("5000");
|
||||
cy.log("Response code check successful");
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.CreateAPI("FirstAPI");
|
||||
cy.log("Creation of FirstAPI Action successful");
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
|
||||
cy.WaitAutoSave();
|
||||
cy.RunAPI();
|
||||
cy.ResponseStatusCheck(testdata.successStatusCode);
|
||||
cy.get(apiwidget.createApiOnSideBar)
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ describe("Text-Table Binding Functionality", function() {
|
|||
.find(".tr")
|
||||
.then(listing => {
|
||||
const listingCount = listing.length.toString();
|
||||
cy.get(commonlocators.TextInside).should("have.text", listingCount);
|
||||
cy.get(commonlocators.TextInside).contains(listingCount);
|
||||
cy.EvaluateDataType("string");
|
||||
cy.EvaluateCurrentValue(listingCount);
|
||||
cy.PublishtheApp();
|
||||
|
|
@ -73,10 +73,7 @@ describe("Text-Table Binding Functionality", function() {
|
|||
.find(".tr")
|
||||
.then(listing => {
|
||||
const listingCountP = listing.length.toString();
|
||||
cy.get(commonlocators.TextInside).should(
|
||||
"have.text",
|
||||
listingCountP,
|
||||
);
|
||||
cy.get(commonlocators.TextInside).contains(listingCountP);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -96,14 +93,14 @@ describe("Text-Table Binding Functionality", function() {
|
|||
*/
|
||||
cy.readTabledata("1", "2").then(tabData => {
|
||||
const tabValue = `\"${tabData}\"`;
|
||||
cy.get(commonlocators.TextInside).should("have.text", tabValue);
|
||||
cy.get(commonlocators.TextInside).contains(tabValue);
|
||||
cy.EvaluateDataType("string");
|
||||
cy.EvaluateCurrentValue(tabValue);
|
||||
cy.PublishtheApp();
|
||||
cy.isSelectRow(1);
|
||||
cy.readTabledataPublish("1", "2").then(tabDataP => {
|
||||
const tabValueP = `\"${tabDataP}\"`;
|
||||
cy.get(commonlocators.TextInside).should("have.text", tabValueP);
|
||||
cy.get(commonlocators.TextInside).contains(tabValueP);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -691,6 +691,7 @@ Cypress.Commands.add("EvaluateDataType", dataType => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add("EvaluateCurrentValue", currentValue => {
|
||||
cy.wait(2000);
|
||||
cy.get(commonlocators.evaluatedCurrentValue)
|
||||
.should("be.visible")
|
||||
.contains(currentValue);
|
||||
|
|
|
|||
|
|
@ -68,10 +68,10 @@ export const runAction = (id: string, paginationField?: PaginationField) => {
|
|||
};
|
||||
|
||||
export const updateAction = (payload: { id: string }) => {
|
||||
return {
|
||||
return batchAction({
|
||||
type: ReduxActionTypes.UPDATE_ACTION_INIT,
|
||||
payload,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const updateActionSuccess = (payload: { data: Action }) => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import DynamicTextField from "components/editorComponents/form/fields/DynamicTex
|
|||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import { InputType } from "widgets/InputWidget";
|
||||
import HelperTooltip from "components/editorComponents/HelperTooltip";
|
||||
import { Colors } from "constants/Colors";
|
||||
|
||||
const FormRowWithLabel = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -105,13 +106,14 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
|
|||
onClick={() =>
|
||||
props.fields.push({ key: "", value: "" })
|
||||
}
|
||||
color={"#A3B3BF"}
|
||||
color={Colors["CADET_BLUE"]}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
) : (
|
||||
<FormIcons.DELETE_ICON
|
||||
height={20}
|
||||
width={20}
|
||||
color={Colors["CADET_BLUE"]}
|
||||
onClick={() => props.fields.remove(index)}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {
|
|||
select,
|
||||
takeEvery,
|
||||
takeLatest,
|
||||
debounce,
|
||||
} from "redux-saga/effects";
|
||||
import ActionAPI, { ActionCreateUpdateResponse, Property } from "api/ActionAPI";
|
||||
import _ from "lodash";
|
||||
|
|
@ -481,7 +480,7 @@ export function* watchActionSagas() {
|
|||
fetchActionsForViewModeSaga,
|
||||
),
|
||||
takeEvery(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga),
|
||||
debounce(500, ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),
|
||||
takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),
|
||||
takeLatest(ReduxActionTypes.DELETE_ACTION_INIT, deleteActionSaga),
|
||||
takeLatest(ReduxActionTypes.SAVE_API_NAME, saveApiNameSaga),
|
||||
takeLatest(ReduxActionTypes.MOVE_ACTION_INIT, moveActionSaga),
|
||||
|
|
|
|||
|
|
@ -25,9 +25,13 @@ const BATCH_PRIORITY = {
|
|||
needsSaga: true,
|
||||
},
|
||||
[ReduxActionTypes.UPDATE_ACTION_PROPERTY]: {
|
||||
priority: 1,
|
||||
priority: 0,
|
||||
needsSaga: false,
|
||||
},
|
||||
[ReduxActionTypes.UPDATE_ACTION_INIT]: {
|
||||
priority: 1,
|
||||
needsSaga: true,
|
||||
},
|
||||
};
|
||||
|
||||
const batches: ReduxAction<any>[][] = [];
|
||||
|
|
|
|||
|
|
@ -232,11 +232,24 @@ function* createDatasourceFromFormSaga(
|
|||
}
|
||||
|
||||
if (subSection.initialValue) {
|
||||
_.set(
|
||||
initialValues,
|
||||
subSection.configProperty,
|
||||
subSection.initialValue,
|
||||
);
|
||||
if (subSection.controlType === "KEYVALUE_ARRAY") {
|
||||
subSection.initialValue.forEach(
|
||||
(initialValue: string | number, index: number) => {
|
||||
const configProperty = subSection.configProperty.replace(
|
||||
"*",
|
||||
index,
|
||||
);
|
||||
|
||||
_.set(initialValues, configProperty, initialValue);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
_.set(
|
||||
initialValues,
|
||||
subSection.configProperty,
|
||||
subSection.initialValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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())))));
|
||||
}
|
||||
}
|
||||
|
|
@ -116,18 +116,15 @@ read -p 'Installation Directory [appsmith]: ' install_dir
|
|||
install_dir=${install_dir:-appsmith}
|
||||
mkdir -p $PWD/$install_dir
|
||||
install_dir=$PWD/$install_dir
|
||||
echo "Appsmith needs a mongodb instance to run"
|
||||
echo "1) Automatically setup mongo db on this instance (recommended)"
|
||||
echo "2) Connect to an external mongo db"
|
||||
read -p 'Enter option number [1]: ' mongo_option
|
||||
mongo_option=${mongo_option:-1}
|
||||
read -p 'Is this a fresh installation? [Y/n]' fresh_install
|
||||
fresh_install=${fresh_install:-Y}
|
||||
echo ""
|
||||
|
||||
if [[ $mongo_option -eq 2 ]];then
|
||||
read -p 'Enter your mongo db host: ' mongo_host
|
||||
read -p 'Enter the mongo root user: ' mongo_root_user
|
||||
read -sp 'Enter the mongo password: ' mongo_root_password
|
||||
read -p 'Enter your mongo database name: ' mongo_database
|
||||
if [ $fresh_install == "N" -o $fresh_install == "n" -o $fresh_install == "no" -o $fresh_install == "No" ];then
|
||||
read -p 'Enter your current mongo db host: ' mongo_host
|
||||
read -p 'Enter your current mongo root user: ' mongo_root_user
|
||||
read -sp 'Enter your current mongo password: ' mongo_root_password
|
||||
read -p 'Enter your current mongo database name: ' mongo_database
|
||||
# It is possible that this isn't the first installation.
|
||||
echo ""
|
||||
read -p 'Do you have any existing data in the database?[Y/n]: ' existing_encrypted_data
|
||||
|
|
@ -138,7 +135,8 @@ if [[ $mongo_option -eq 2 ]];then
|
|||
else
|
||||
auto_generate_encryption="false"
|
||||
fi
|
||||
elif [[ $mongo_option -eq 1 ]];then
|
||||
elif [ $fresh_install == "Y" -o $fresh_install == "y" -o $fresh_install == "yes" -o $fresh_install == "Yes" ];then
|
||||
echo "Appsmith needs to configure a mongo db to run"
|
||||
mongo_host="mongo"
|
||||
mongo_database="appsmith"
|
||||
read -p 'Set the mongo root user: ' mongo_root_user
|
||||
|
|
@ -186,11 +184,11 @@ echo ""
|
|||
read -p 'Would you like to host appsmith on a custom domain / subdomain? [Y/n]: ' setup_domain
|
||||
setup_domain=${setup_domain:-Y}
|
||||
if [ $setup_domain == "Y" -o $setup_domain == "y" -o $setup_domain == "yes" -o $setup_domain == "Yes" ];then
|
||||
echo "+++++++++++++++++++++++++++++++++"
|
||||
echo "+++++++++++ IMPORTANT PLEASE READ ++++++++++++++++++++++"
|
||||
echo "Please update your DNS records with your domain registrar"
|
||||
echo "You can read more about this in our Documentation"
|
||||
echo "https://docs.appsmith.com/v/v1.1/quick-start#custom-domains"
|
||||
echo "+++++++++++++++++++++++++++++++++"
|
||||
echo "+++++++++++++++++++++++++++++++++++++++++++++++"
|
||||
echo "Would you like to provision an SSL certificate for your custom domain / subdomain?"
|
||||
read -p '(Your DNS records must be updated for us to provision SSL) [Y/n]: ' setup_ssl
|
||||
setup_ssl=${setup_ssl:-Y}
|
||||
|
|
@ -199,7 +197,7 @@ else
|
|||
fi
|
||||
|
||||
if [ $setup_ssl == "Y" -o $setup_ssl == "y" -o $setup_ssl == "yes" -o $setup_ssl == "Yes" ];then
|
||||
read -p 'Enter your domain / subdomain name (example.com / app.example.com): ' custom_domain
|
||||
read -p 'Enter the domain or subdomain on which you want to host appsmith (example.com / app.example.com): ' custom_domain
|
||||
fi
|
||||
|
||||
NGINX_SSL_CMNT=""
|
||||
|
|
@ -222,8 +220,11 @@ if ! is_command_present docker ;then
|
|||
if [ $package_manager == "apt-get" -o $package_manager == "yum" ];then
|
||||
install_docker
|
||||
else
|
||||
echo "Please follow below link to Install Docker Desktop on Mac:"
|
||||
echo "+++++++++++ IMPORTANT READ ++++++++++++++++++++++"
|
||||
echo "Docker Desktop must be installed manually on Mac OS to proceed. Docker will be installed automatically on Ubuntu / Redhat / Cent OS"
|
||||
echo "https://docs.docker.com/docker-for-mac/install/"
|
||||
echo "++++++++++++++++++++++++++++++++++++++++++++++++"
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user