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);
});
}