diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index 055d5a11a4..140cc01404 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -75,7 +75,6 @@ jobs: ui-test: needs: build runs-on: ubuntu-latest - # container: appsmith/cypress-nginx defaults: run: working-directory: app/client diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index 68ab8f47bb..c42d99b7ee 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -82,3 +82,36 @@ jobs: 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 + + # These are dummy jobs in the CI build to satisfy required status checks for merging PRs. This is a hack because Github doesn't support conditional + # required checks in monorepos. These jobs are a clone of similarly named jobs in client.yml. + # + # Check support request at: https://github.community/t/feature-request-conditional-required-checks/16761 + ui-test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + job: [0, 1, 2, 3, 4, 5, 6] + + steps: + # Checkout the code + - uses: actions/checkout@v2 + + - name: Do nothing as this is a dummy step + shell: bash + run: | + exit 0 + + package: + runs-on: ubuntu-latest + + steps: + # Checkout the code + - uses: actions/checkout@v2 + + - name: Do nothing as this is a dummy step + shell: bash + run: | + exit 0 + diff --git a/app/client/src/api/ApplicationApi.tsx b/app/client/src/api/ApplicationApi.tsx index 3f8b1ac6ad..205fe34e3c 100644 --- a/app/client/src/api/ApplicationApi.tsx +++ b/app/client/src/api/ApplicationApi.tsx @@ -26,6 +26,7 @@ export interface ApplicationResponsePayload { name: string; organizationId: string; pages?: ApplicationPagePayload[]; + appIsExample: boolean; } // export interface FetchApplicationResponse extends ApiResponse { diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 955a2bb57a..bd0ac95b98 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -401,6 +401,7 @@ export type ApplicationPayload = { defaultPageId?: string; isPublic?: boolean; userPermissions?: string[]; + appIsExample: boolean; }; export type OrganizationDetails = { diff --git a/app/client/src/constants/messages.ts b/app/client/src/constants/messages.ts index aa9a0e2b7f..bb171a5306 100644 --- a/app/client/src/constants/messages.ts +++ b/app/client/src/constants/messages.ts @@ -137,6 +137,7 @@ export const TIMEZONE = "Timezone"; export const ENABLE_TIME = "Enable Time"; export const EDIT_APP = "Back to editor"; +export const FORK_APP = "Fork App"; export const LIGHTNING_MENU_DATA_API = "Use data from an API"; export const LIGHTNING_MENU_DATA_QUERY = "Use data from a Query"; diff --git a/app/client/src/mockComponentProps/ApplicationPayloads.tsx b/app/client/src/mockComponentProps/ApplicationPayloads.tsx index 0ea6a31af4..1102d9b205 100644 --- a/app/client/src/mockComponentProps/ApplicationPayloads.tsx +++ b/app/client/src/mockComponentProps/ApplicationPayloads.tsx @@ -4,6 +4,7 @@ export const getApplicationPayload = (): ApplicationPayload => ({ id: generateReactKey(), name: generateReactKey(), organizationId: generateReactKey(), + appIsExample: false, pageCount: 4, }); diff --git a/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx b/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx index 0030dfd47e..b75633df71 100644 --- a/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx +++ b/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx @@ -4,7 +4,7 @@ import styled from "styled-components"; import StyledHeader from "components/designSystems/appsmith/StyledHeader"; import AppsmithLogo from "assets/images/appsmith_logo_white.png"; import Button from "components/editorComponents/Button"; -import { EDIT_APP } from "constants/messages"; +import { EDIT_APP, FORK_APP } from "constants/messages"; import { isPermitted, PERMISSION_TYPE, @@ -16,6 +16,7 @@ import { import { APPLICATIONS_URL, getApplicationViewerPageURL, + SIGN_UP_URL, } from "constants/routes"; import { connect } from "react-redux"; import { AppState } from "reducers"; @@ -60,6 +61,12 @@ const BackToEditorButton = styled(Button)` margin: 5px 10px; `; +const ForkButton = styled(Button)` + max-width: 200px; + height: 32px; + margin: 5px 10px; +`; + const ShareButton = styled(Button)` height: 32px; margin: 5px 10px; @@ -116,10 +123,41 @@ type AppViewerHeaderProps = { export const AppViewerHeader = (props: AppViewerHeaderProps) => { const { currentApplicationDetails, pages, currentOrgId } = props; + const isExampleApp = currentApplicationDetails?.appIsExample; const userPermissions = currentApplicationDetails?.userPermissions ?? []; const permissionRequired = PERMISSION_TYPE.MANAGE_APPLICATION; const canEdit = isPermitted(userPermissions, permissionRequired); + const forkAppUrl = `${window.location.origin}${SIGN_UP_URL}?appId=${currentApplicationDetails?.id}`; + + let CTA = null; + + if (props.url && canEdit) { + CTA = ( + + ); + } else if (isExampleApp) { + CTA = ( + + ); + } + return ( 1}> @@ -160,18 +198,7 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => { applicationId={currentApplicationDetails.id} title={currentApplicationDetails.name} /> - - {props.url && canEdit && ( - - )} + {CTA} )} diff --git a/app/client/src/pages/UserAuth/SignUp.tsx b/app/client/src/pages/UserAuth/SignUp.tsx index ba48a470d3..72bc96800a 100644 --- a/app/client/src/pages/UserAuth/SignUp.tsx +++ b/app/client/src/pages/UserAuth/SignUp.tsx @@ -94,7 +94,9 @@ export const SignUp = (props: InjectedFormProps) => { } let signupURL = "/api/v1/" + SIGNUP_SUBMIT_PATH; - if (queryParams.has("redirectTo")) { + if (queryParams.has("appId")) { + signupURL += `?appId=${queryParams.get("appId")}`; + } else if (queryParams.has("redirectTo")) { signupURL += `?redirectUrl=${queryParams.get("redirectTo")}`; } diff --git a/app/client/src/sagas/ApplicationSagas.tsx b/app/client/src/sagas/ApplicationSagas.tsx index d51099fa76..2608f968bf 100644 --- a/app/client/src/sagas/ApplicationSagas.tsx +++ b/app/client/src/sagas/ApplicationSagas.tsx @@ -114,6 +114,7 @@ export function* fetchApplicationListSaga() { id: application.id, pageCount: application.pages ? application.pages.length : 0, defaultPageId: getDefaultPageId(application.pages), + appIsExample: application.appIsExample, }), ); yield put({ @@ -287,6 +288,7 @@ export function* createApplicationSaga( organizationId: response.data.organizationId, pageCount: response.data.pages ? response.data.pages.length : 0, defaultPageId: getDefaultPageId(response.data.pages), + appIsExample: response.data.appIsExample, }; AnalyticsUtil.logEvent("CREATE_APP", { appName: application.name, diff --git a/app/client/src/sagas/QueryPaneSagas.ts b/app/client/src/sagas/QueryPaneSagas.ts index 94d9d591e2..1edf1f7bfd 100644 --- a/app/client/src/sagas/QueryPaneSagas.ts +++ b/app/client/src/sagas/QueryPaneSagas.ts @@ -26,7 +26,6 @@ import { import { RestAction } from "entities/Action"; import { setActionProperty } from "actions/actionActions"; import { fetchPluginForm } from "actions/pluginActions"; -import { changeQuery } from "actions/queryPaneActions"; function* changeQuerySaga(actionPayload: ReduxAction<{ id: string }>) { const { id } = actionPayload.payload; diff --git a/app/client/src/utils/formhelpers.ts b/app/client/src/utils/formhelpers.ts index f6dcf550e6..e665bf67ca 100644 --- a/app/client/src/utils/formhelpers.ts +++ b/app/client/src/utils/formhelpers.ts @@ -14,6 +14,6 @@ export const isStrongPassword = (value: string) => { // TODO (abhinav): Use a regex which adheres to standards RFC5322 export const isEmail = (value: string) => { - const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(value); }; diff --git a/app/server/README.md b/app/server/README.md index 9e32d92265..e6450c3c95 100644 --- a/app/server/README.md +++ b/app/server/README.md @@ -12,7 +12,7 @@ For example: $ ./build.sh -DskipTests ``` -This will +This script will perform the following steps: 1. Compile the code 2. Generate the jars for server & plugins 3. Copy them into the `dist` directory diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/PolicyGenerator.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/PolicyGenerator.java index 3903eac04a..dc40bf3b16 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/PolicyGenerator.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/PolicyGenerator.java @@ -176,11 +176,11 @@ public class PolicyGenerator { return childPolicySet; } - public Set getAllChildPolicies(Set policySet, Class inheritingEntity, Class destinationEntity) { + public Set getAllChildPolicies(Set policySet, Class sourceEntity, Class destinationEntity) { Set policies = policySet.stream() .map(policy -> { AclPermission aclPermission = AclPermission - .getPermissionByValue(policy.getPermission(), inheritingEntity); + .getPermissionByValue(policy.getPermission(), sourceEntity); // Get all the child policies for the given policy and aclPermission return getChildPolicies(policy, aclPermission, destinationEntity); }).flatMap(Collection::stream) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java index 0658fd185a..e2e691c57e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java @@ -7,7 +7,6 @@ import com.appsmith.server.acl.PolicyGenerator; 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.repositories.ActionRepository; @@ -27,13 +26,6 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS; -import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES; -import static com.appsmith.server.acl.AclPermission.ORGANIZATION_MANAGE_APPLICATIONS; -import static com.appsmith.server.acl.AclPermission.ORGANIZATION_READ_APPLICATIONS; -import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS; -import static com.appsmith.server.acl.AclPermission.READ_PAGES; - @Component public class PolicyUtils { @@ -148,18 +140,6 @@ public class PolicyUtils { .collect(Collectors.toMap(Policy::getPermission, Function.identity())); } - public Map generateChildrenPoliciesFromOrganizationPolicies(Map orgPolicyMap, Class destinationEntity) { - Set extractedInterestingPolicySet = new HashSet<>(orgPolicyMap.values()) - .stream() - .filter(policy -> policy.getPermission().equals(ORGANIZATION_MANAGE_APPLICATIONS.getValue()) - || policy.getPermission().equals(ORGANIZATION_READ_APPLICATIONS.getValue())) - .collect(Collectors.toSet()); - - return policyGenerator.getAllChildPolicies(extractedInterestingPolicySet, Organization.class, destinationEntity) - .stream() - .collect(Collectors.toMap(Policy::getPermission, Function.identity())); - } - public Flux updateWithNewPoliciesToDatasourcesByOrgId(String orgId, Map newPoliciesMap, boolean addPolicyToObject) { return datasourceRepository @@ -194,18 +174,6 @@ public class PolicyUtils { .flatMapMany(updatedApplications -> applicationRepository.saveAll(updatedApplications)); } - public Map generatePagePoliciesFromApplicationPolicies(Map applicationPolicyMap) { - Set extractedInterestingPolicySet = new HashSet<>(applicationPolicyMap.values()) - .stream() - .filter(policy -> policy.getPermission().equals(MANAGE_APPLICATIONS.getValue()) - || policy.getPermission().equals(READ_APPLICATIONS.getValue())) - .collect(Collectors.toSet()); - - return policyGenerator.getAllChildPolicies(extractedInterestingPolicySet, Application.class, Page.class) - .stream() - .collect(Collectors.toMap(Policy::getPermission, Function.identity())); - } - public Flux updateWithApplicationPermissionsToAllItsPages(String applicationId, Map newPagePoliciesMap, boolean addPolicyToObject) { return pageRepository @@ -222,18 +190,6 @@ public class PolicyUtils { .flatMapMany(updatedPages -> pageRepository.saveAll(updatedPages)); } - public Map generateActionPoliciesFromPagePolicies(Map pagePolicyMap) { - Set extractedInterestingPolicySet = new HashSet<>(pagePolicyMap.values()) - .stream() - .filter(policy -> policy.getPermission().equals(MANAGE_PAGES.getValue()) - || policy.getPermission().equals(READ_PAGES.getValue())) - .collect(Collectors.toSet()); - - return policyGenerator.getAllChildPolicies(extractedInterestingPolicySet, Page.class, Action.class) - .stream() - .collect(Collectors.toMap(Policy::getPermission, Function.identity())); - } - public Flux updateWithPagePermissionsToAllItsActions(String pageId, Map newActionPoliciesMap, boolean addPolicyToObject) { return actionRepository @@ -255,4 +211,14 @@ public class PolicyUtils { .collectList() .flatMapMany(updatedActions -> actionRepository.saveAll(updatedActions)); } + + public Map generateInheritedPoliciesFromSourcePolicies(Map sourcePolicyMap, + Class sourceEntity, + Class destinationEntity) { + Set extractedInterestingPolicySet = new HashSet<>(sourcePolicyMap.values()); + + return policyGenerator.getAllChildPolicies(extractedInterestingPolicySet, sourceEntity, destinationEntity) + .stream() + .collect(Collectors.toMap(Policy::getPermission, Function.identity())); + } } 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 0625d61916..57121a8626 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 @@ -213,8 +213,8 @@ public class ApplicationServiceImpl extends BaseService applicationPolicyMap = policyUtils.generatePolicyFromPermission(Set.of(applicationPermission), user); - Map pagePolicyMap = policyUtils.generatePagePoliciesFromApplicationPolicies(applicationPolicyMap); - Map actionPolicyMap = policyUtils.generateActionPoliciesFromPagePolicies(pagePolicyMap); + Map pagePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(applicationPolicyMap, Application.class, Page.class); + Map actionPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(pagePolicyMap, Page.class, Action.class); Map datasourcePolicyMap = policyUtils.generatePolicyFromPermission(Set.of(datasourcePermission), user); Flux updatedPagesFlux = policyUtils.updateWithApplicationPermissionsToAllItsPages(application.getId(), pagePolicyMap, isPublic); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java index 5b40a3dea1..482f485c5f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java @@ -152,10 +152,10 @@ public class UserOrganizationServiceImpl implements UserOrganizationService { // Generate all the policies for Organization, Application, Page and Actions for the current user Set rolePermissions = role.getPermissions(); Map orgPolicyMap = policyUtils.generatePolicyFromPermission(rolePermissions, user); - Map applicationPolicyMap = policyUtils.generateChildrenPoliciesFromOrganizationPolicies(orgPolicyMap, Application.class); - Map datasourcePolicyMap = policyUtils.generateChildrenPoliciesFromOrganizationPolicies(orgPolicyMap, Datasource.class); - Map pagePolicyMap = policyUtils.generatePagePoliciesFromApplicationPolicies(applicationPolicyMap); - Map actionPolicyMap = policyUtils.generateActionPoliciesFromPagePolicies(pagePolicyMap); + Map applicationPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(orgPolicyMap, Organization.class, Application.class); + Map datasourcePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(orgPolicyMap, Organization.class, Datasource.class); + Map pagePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(applicationPolicyMap, Application.class, Page.class); + Map actionPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(pagePolicyMap, Page.class, Action.class); //Now update the organization policies Organization updatedOrganization = policyUtils.addPoliciesToExistingObject(orgPolicyMap, organization); @@ -215,10 +215,10 @@ public class UserOrganizationServiceImpl implements UserOrganizationService { // Generate all the policies for Organization, Application, Page and Actions Set rolePermissions = role.getPermissions(); Map orgPolicyMap = policyUtils.generatePolicyFromPermission(rolePermissions, user); - Map applicationPolicyMap = policyUtils.generateChildrenPoliciesFromOrganizationPolicies(orgPolicyMap, Application.class); - Map datasourcePolicyMap = policyUtils.generateChildrenPoliciesFromOrganizationPolicies(orgPolicyMap, Datasource.class); - Map pagePolicyMap = policyUtils.generatePagePoliciesFromApplicationPolicies(applicationPolicyMap); - Map actionPolicyMap = policyUtils.generateActionPoliciesFromPagePolicies(pagePolicyMap); + Map applicationPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(orgPolicyMap, Organization.class, Application.class); + Map datasourcePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(orgPolicyMap, Organization.class, Datasource.class); + Map pagePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(applicationPolicyMap, Application.class, Page.class); + Map actionPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(pagePolicyMap, Page.class, Action.class); //Now update the organization policies Organization updatedOrganization = policyUtils.removePoliciesFromExistingObject(orgPolicyMap, organization); @@ -333,10 +333,10 @@ public class UserOrganizationServiceImpl implements UserOrganizationService { // Generate all the policies for Organization, Application, Page and Actions for the current user Set rolePermissions = role.getPermissions(); Map orgPolicyMap = policyUtils.generatePolicyFromPermissionForMultipleUsers(rolePermissions, users); - Map applicationPolicyMap = policyUtils.generateChildrenPoliciesFromOrganizationPolicies(orgPolicyMap, Application.class); - Map datasourcePolicyMap = policyUtils.generateChildrenPoliciesFromOrganizationPolicies(orgPolicyMap, Datasource.class); - Map pagePolicyMap = policyUtils.generatePagePoliciesFromApplicationPolicies(applicationPolicyMap); - Map actionPolicyMap = policyUtils.generateActionPoliciesFromPagePolicies(pagePolicyMap); + Map applicationPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(orgPolicyMap, Organization.class, Application.class); + Map datasourcePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(orgPolicyMap, Organization.class, Datasource.class); + Map pagePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(applicationPolicyMap, Application.class, Page.class); + Map actionPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(pagePolicyMap, Page.class, Action.class); //Now update the organization policies Organization updatedOrganization = (Organization) policyUtils.addPoliciesToExistingObject(orgPolicyMap, organization); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java index 7e6b7773db..8862b1b9ee 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java @@ -282,8 +282,7 @@ public class UserServiceImpl extends BaseService i .findByEmail(user.getEmail()) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "token", token))) .flatMap(passwordResetTokenRepository::delete) - .thenReturn(userFromDb) - .flatMap(repository::save) + .then(repository.save(userFromDb)) .thenReturn(true); }); }