Merge branch 'release' of https://github.com/appsmithorg/appsmith into release

This commit is contained in:
Automated Github Action 2020-08-14 06:06:03 +00:00
commit c47e66cf0c
9 changed files with 157 additions and 276 deletions

View File

@ -3,7 +3,12 @@ import styled from "styled-components";
import HighlightedCode, {
SYNTAX_HIGHLIGHTING_SUPPORTED_LANGUAGES,
} from "components/editorComponents/HighlightedCode";
import { Popover, PopoverInteractionKind, Classes } from "@blueprintjs/core";
import {
Popover,
PopoverInteractionKind,
Classes,
Icon,
} from "@blueprintjs/core";
import { CurrentValueViewer } from "components/editorComponents/CodeEditor/EvaluatedValuePopup";
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
import useClipboard from "utils/hooks/useClipboard";
@ -61,9 +66,15 @@ const Wrapper = styled.div<{ step: number }>`
padding-top: 4px;
padding-bottom: 4px;
cursor: pointer;
& ~ span.${Classes.ICON} {
position: absolute;
right: 4px;
top: 10px;
opacity: 0;
}
&:hover {
&:before {
content: "Copy";
content: "";
background: ${Colors.TUNDORA};
opacity: 0.5;
position: absolute;
@ -71,14 +82,11 @@ const Wrapper = styled.div<{ step: number }>`
height: 100%;
top: 0;
width: 100%;
font-size: 12px;
color: white;
display: flex;
justify-content: flex-end;
align-items: center;
text-align: right;
z-index: 1;
}
& ~ span.${Classes.ICON} {
opacity: 1;
}
}
}
@ -92,6 +100,7 @@ const Wrapper = styled.div<{ step: number }>`
text-shadow: none;
padding-left: ${props =>
props.step * props.theme.spaces[2] + props.theme.spaces[3]}px;
padding-right: 20px;
& span.token.property {
overflow: hidden;
white-space: nowrap;
@ -214,6 +223,7 @@ export const EntityProperty = memo((props: EntityPropertyProps) => {
codeText={codeText}
language={SYNTAX_HIGHLIGHTING_SUPPORTED_LANGUAGES.APPSMITH}
/>
<Icon icon="duplicate" iconSize={14} color={Colors.ALTO} />
{propertyValue}
</Wrapper>
);

View File

@ -27,7 +27,7 @@ export const ExplorerPageEntity = memo((props: ExplorerPageEntityProps) => {
if (!!params.applicationId) {
history.push(BUILDER_PAGE_URL(params.applicationId, props.page.pageId));
}
}, [props.isCurrentPage, props.page.pageId, params.applicationId]);
}, [props.page.pageId, params.applicationId]);
const contextMenu = (
<PageContextMenu

View File

@ -1,216 +0,0 @@
import React, { useLayoutEffect } from "react";
import { AppState } from "reducers";
import { withRouter, RouteComponentProps } from "react-router-dom";
import { connect } from "react-redux";
import { InjectedFormProps, reduxForm, Field } from "redux-form";
import { CREATE_PASSWORD_FORM_NAME } from "constants/forms";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
import { getIsTokenValid, getIsValidatingToken } from "selectors/authSelectors";
import FormTextField from "components/editorComponents/form/FormTextField";
import FormMessage, {
FormMessageProps,
MessageAction,
} from "components/editorComponents/form/FormMessage";
import Spinner from "components/editorComponents/Spinner";
import Button from "components/editorComponents/Button";
import FormGroup from "components/editorComponents/form/FormGroup";
import StyledForm from "components/editorComponents/Form";
import { isEmptyString, isStrongPassword } from "utils/formhelpers";
import {
CreatePasswordFormValues,
createPasswordSubmitHandler,
} from "./helpers";
import {
AuthCardHeader,
AuthCardFooter,
AuthCardContainer,
AuthCardBody,
AuthCardNavLink,
FormActions,
} from "./StyledComponents";
import { AUTH_LOGIN_URL } from "constants/routes";
import {
CREATE_PASSWORD_PAGE_PASSWORD_INPUT_LABEL,
CREATE_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER,
CREATE_PASSWORD_LOGIN_LINK_TEXT,
CREATE_PASSWORD_SUBMIT_BUTTON_TEXT,
CREATE_PASSWORD_PAGE_SUBTITLE,
CREATE_PASSWORD_PAGE_TITLE,
FORM_VALIDATION_INVALID_PASSWORD,
FORM_VALIDATION_EMPTY_PASSWORD,
CREATE_PASSWORD_EXPIRED_TOKEN,
CREATE_PASSWORD_INVALID_TOKEN,
CREATE_PASSWORD_RESET_SUCCESS,
CREATE_PASSWORD_RESET_SUCCESS_LOGIN_LINK,
} from "constants/messages";
import { TncPPLinks } from "./SignUp";
const validate = (values: CreatePasswordFormValues) => {
const errors: CreatePasswordFormValues = {};
if (!values.password || isEmptyString(values.password)) {
errors.password = FORM_VALIDATION_EMPTY_PASSWORD;
} else if (!isStrongPassword(values.password)) {
errors.password = FORM_VALIDATION_INVALID_PASSWORD;
}
return errors;
};
type CreatePasswordProps = InjectedFormProps<
CreatePasswordFormValues,
{
verifyToken: (token: string, email: string) => void;
isTokenValid: boolean;
validatingToken: boolean;
}
> & {
verifyToken: (token: string, email: string) => void;
isTokenValid: boolean;
validatingToken: boolean;
} & RouteComponentProps<{ email: string; token: string }>;
export const CreatePassword = (props: CreatePasswordProps) => {
const {
error,
handleSubmit,
pristine,
submitting,
submitSucceeded,
submitFailed,
initialValues,
isTokenValid,
validatingToken,
verifyToken,
valid,
} = props;
useLayoutEffect(() => {
if (initialValues.token && initialValues.email)
verifyToken(initialValues.token, initialValues.email);
}, [initialValues.token, initialValues.email, verifyToken]);
const showInvalidMessage = !initialValues.token || !initialValues.email;
const showExpiredMessage = !isTokenValid && !validatingToken;
const showSuccessMessage = submitSucceeded && !pristine;
const showFailureMessage = submitFailed && !!error;
let message = "";
let messageActions: MessageAction[] | undefined = undefined;
if (showExpiredMessage) {
message = CREATE_PASSWORD_EXPIRED_TOKEN;
}
if (showInvalidMessage) {
message = CREATE_PASSWORD_INVALID_TOKEN;
}
if (showSuccessMessage) {
message = CREATE_PASSWORD_RESET_SUCCESS;
messageActions = [
{
url: AUTH_LOGIN_URL,
text: CREATE_PASSWORD_RESET_SUCCESS_LOGIN_LINK,
intent: "primary",
},
];
}
if (showFailureMessage) {
message = error;
}
const messageTagProps: FormMessageProps = {
intent:
showInvalidMessage || showExpiredMessage || showFailureMessage
? "danger"
: "primary",
message,
actions: messageActions,
};
if (showInvalidMessage || showExpiredMessage) {
return <FormMessage {...messageTagProps} />;
}
if (!isTokenValid && validatingToken) {
return <Spinner />;
}
return (
<AuthCardContainer>
{(showSuccessMessage || showFailureMessage) && (
<FormMessage {...messageTagProps} />
)}
<AuthCardHeader>
<h1>{CREATE_PASSWORD_PAGE_TITLE}</h1>
<h5>{CREATE_PASSWORD_PAGE_SUBTITLE}</h5>
</AuthCardHeader>
<AuthCardBody>
<StyledForm onSubmit={handleSubmit(createPasswordSubmitHandler)}>
<FormGroup
intent={error ? "danger" : "none"}
label={CREATE_PASSWORD_PAGE_PASSWORD_INPUT_LABEL}
>
<FormTextField
name="password"
type="password"
placeholder={CREATE_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER}
/>
</FormGroup>
<Field type="hidden" name="email" component="input" />
<Field type="hidden" name="token" component="input" />
<FormActions>
<Button
filled
size="large"
type="submit"
text={CREATE_PASSWORD_SUBMIT_BUTTON_TEXT}
intent="primary"
disabled={pristine || !valid}
loading={submitting}
/>
</FormActions>
</StyledForm>
</AuthCardBody>
<AuthCardNavLink to={AUTH_LOGIN_URL}>
{CREATE_PASSWORD_LOGIN_LINK_TEXT}
</AuthCardNavLink>
<AuthCardFooter>
<TncPPLinks></TncPPLinks>
</AuthCardFooter>
</AuthCardContainer>
);
};
export default connect(
(state: AppState, props: CreatePasswordProps) => {
const queryParams = new URLSearchParams(props.location.search);
return {
initialValues: {
email: queryParams.get("email") || undefined,
token: queryParams.get("token") || undefined,
},
isTokenValid: getIsTokenValid(state),
validatingToken: getIsValidatingToken(state),
};
},
(dispatch: any) => ({
verifyToken: (token: string, email: string) =>
dispatch({
type: ReduxActionTypes.VERIFY_INVITE_INIT,
payload: { token, email },
}),
}),
)(
reduxForm<
CreatePasswordFormValues,
{
verifyToken: (token: string, email: string) => void;
validatingToken: boolean;
isTokenValid: boolean;
}
>({
validate,
form: CREATE_PASSWORD_FORM_NAME,
touchOnBlur: true,
})(withRouter(CreatePassword)),
);

View File

@ -2,7 +2,7 @@ import React from "react";
import { reduxForm, InjectedFormProps } from "redux-form";
import { AUTH_LOGIN_URL } from "constants/routes";
import { SIGNUP_FORM_NAME } from "constants/forms";
import { Link, useLocation } from "react-router-dom";
import { Link, RouteComponentProps, useLocation, withRouter } from "react-router-dom";
import Divider from "components/editorComponents/Divider";
import {
AuthCardHeader,
@ -43,6 +43,8 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
import { getAppsmithConfigs } from "configs";
import { SIGNUP_SUBMIT_PATH } from "constants/ApiConstants";
import { connect } from "react-redux";
import { AppState } from "@appsmith/reducers";
const {
enableGithubOAuth,
enableGoogleOAuth,
@ -81,7 +83,9 @@ const validate = (values: SignupFormValues) => {
return errors;
};
export const SignUp = (props: InjectedFormProps<SignupFormValues>) => {
type SignUpFormProps = InjectedFormProps<SignupFormValues> & RouteComponentProps<{ email: string }>;
export const SignUp = (props: SignUpFormProps) => {
const { error, submitting, pristine, valid } = props;
const location = useLocation();
@ -160,8 +164,20 @@ export const SignUp = (props: InjectedFormProps<SignupFormValues>) => {
);
};
export default reduxForm<SignupFormValues>({
validate,
form: SIGNUP_FORM_NAME,
touchOnBlur: true,
})(SignUp);
export default connect(
(state: AppState, props: SignUpFormProps) => {
const queryParams = new URLSearchParams(props.location.search);
return {
initialValues: {
email: queryParams.get("email"),
},
};
},
null,
)(
reduxForm<SignupFormValues>({
validate,
form: SIGNUP_FORM_NAME,
touchOnBlur: true,
})(withRouter(SignUp)),
);

View File

@ -7,8 +7,8 @@ import { AuthContainer, AuthCard } from "./StyledComponents";
import SignUp from "./SignUp";
import ForgotPassword from "./ForgotPassword";
import ResetPassword from "./ResetPassword";
import CreatePassword from "./CreatePassword";
import AppRoute from "pages/common/AppRoute";
import PageNotFound from "pages/common/PageNotFound";
const AnimatedAuthCard = animated(AuthContainer);
export const UserAuth = () => {
const { path } = useRouteMatch();
@ -50,10 +50,8 @@ export const UserAuth = () => {
name={"ForgotPassword"}
/>
<AppRoute
exact
path={`${path}/createPassword`}
component={CreatePassword}
name={"CreatePassword"}
component={PageNotFound}
name={"PageNotFound"}
/>
</Switch>
</AuthCard>

View File

@ -20,7 +20,6 @@ public interface Url {
String TEAM_URL = BASE_URL + VERSION + "/teams";
String GROUP_URL = BASE_URL + VERSION + "/groups";
String PERMISSION_URL = BASE_URL + VERSION + "/permissions";
String SIGNUP_URL = BASE_URL + VERSION + "/signup";
String COLLECTION_URL = BASE_URL + VERSION + "/collections";
String IMPORT_URL = BASE_URL + VERSION + "/import";
String PROVIDER_URL = BASE_URL + VERSION + "/providers";

View File

@ -103,7 +103,9 @@ public class User extends BaseDomain implements UserDetails, OidcUser {
@Override
public boolean isEnabled() {
return this.isEnabled;
// The `isEnabled` field is `Boolean` whereas we are returning `boolean` here. If `isEnabled` field value is
// `null`, this would throw a `NullPointerException`. Hence, checking equality with `Boolean.TRUE` instead.
return Boolean.TRUE.equals(this.isEnabled);
}
// TODO: Check the return value for the functions below to ensure that correct values are being returned

View File

@ -70,7 +70,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
private static final String WELCOME_USER_EMAIL_TEMPLATE = "email/welcomeUserTemplate.html";
private static final String FORGOT_PASSWORD_EMAIL_TEMPLATE = "email/forgotPasswordTemplate.html";
private static final String FORGOT_PASSWORD_CLIENT_URL_FORMAT = "%s/user/resetPassword?token=%s&email=%s";
private static final String INVITE_USER_CLIENT_URL_FORMAT = "%s/user/createPassword?token=%s&email=%s";
private static final String INVITE_USER_CLIENT_URL_FORMAT = "%s/user/signup?token=%s&email=%s";
private static final String INVITE_USER_EMAIL_TEMPLATE = "email/inviteUserCreatorTemplate.html";
private static final String USER_ADDED_TO_ORGANIZATION_EMAIL_TEMPLATE = "email/inviteExistingUserToOrganizationTemplate.html";
// We default the origin header to the production deployment of the client's URL
@ -229,7 +229,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
*
* @param email The email of the user whose password is being reset
* @param token The one-time token provided to the user for resetting the password
* @return
* @return Publishes a boolean indicating whether the given token is valid for the given email address
*/
@Override
public Mono<Boolean> verifyPasswordResetToken(String email, String token) {
@ -425,7 +425,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
public Mono<User> userCreate(User user) {
// Only encode the password if it's a form signup. For OAuth signups, we don't need password
if (user.getIsEnabled() && LoginSource.FORM.equals(user.getSource())) {
if (user.isEnabled() && LoginSource.FORM.equals(user.getSource())) {
if (user.getPassword() == null || user.getPassword().isBlank()) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_CREDENTIALS));
}
@ -465,8 +465,8 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
* <p>
* For new user invite flow, please {@link UserService#inviteUser(InviteUsersDTO, String)}
*
* @param user
* @return
* @param user User object representing the user to be created/enabled.
* @return Publishes the user object, after having been saved.
*/
@Override
public Mono<User> createUserAndSendEmail(User user, String originHeader) {
@ -481,7 +481,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
// If the user doesn't exist, create the user. If the user exists, return a duplicate key exception
return repository.findByEmail(user.getUsername())
.flatMap(savedUser -> {
if (!savedUser.getIsEnabled()) {
if (!savedUser.isEnabled()) {
// First enable the user
savedUser.setIsEnabled(true);
@ -553,7 +553,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
* 2. User exists :
* a. Add user to the organization
* b. Add organization to the user
* @return
* @return Publishes the invited users, after being saved with the new organization ID.
*/
@Override
public Flux<User> inviteUser(InviteUsersDTO inviteUsersDTO, String originHeader) {

View File

@ -104,6 +104,54 @@ overwrite_file() {
fi
}
# This function prompts the user for an input for a non-empty Mongo root password.
read_mongo_password() {
read -sp 'Set the mongo password: ' mongo_root_password
while [[ -z $mongo_root_password ]]
do
echo ""
echo ""
echo "+++++++++++ ERROR ++++++++++++++++++++++"
echo "The mongo password cannot be empty. Please input a valid password string."
echo "++++++++++++++++++++++++++++++++++++++++"
echo ""
read -sp 'Set the mongo password: ' mongo_root_password
done
}
# This function prompts the user for an input for a non-empty Mongo username.
read_mongo_username() {
read -p 'Set the mongo root user: ' mongo_root_user
while [[ -z $mongo_root_user ]]
do
echo ""
echo "+++++++++++ ERROR ++++++++++++++++++++++"
echo "The mongo username cannot be empty. Please input a valid username string."
echo "++++++++++++++++++++++++++++++++++++++++"
echo ""
read -p 'Set the mongo root user: ' mongo_root_user
done
}
wait_for_containers_start() {
timeout=$1
i=1
echo -ne "Waiting for all containers to start. This check will timeout in $timeout seconds ...\r\c"
# The do-while loop is important because for-loops don't work for dynamic values
while [[ $i -le $timeout ]]
do
status_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/api/v1)
# echo $status_code
if [[ status_code -eq 401 ]]; then
break
else
echo -ne "Waiting for all containers to start. This check will timeout in $timeout seconds ...\r\c"
fi
((i = i + 1))
sleep 1
done
}
echo -e "\U1F44B Thank you for trying out Appsmith! "
echo ""
@ -132,6 +180,25 @@ if [[ $ports_occupied -ne 0 ]]; then
exit
fi
# Check is Docker daemon is installed and available. If not, the install & start Docker for Linux machines. We cannot automatically install Docker Desktop on Mac OS
if ! is_command_present docker ;then
if [ $package_manager == "apt-get" -o $package_manager == "yum" ];then
install_docker
else
echo ""
echo "+++++++++++ IMPORTANT READ ++++++++++++++++++++++"
echo "Docker Desktop must be installed manually on Mac OS to proceed. Docker can only be installed automatically on Ubuntu / Redhat / Cent OS"
echo "https://docs.docker.com/docker-for-mac/install/"
echo "++++++++++++++++++++++++++++++++++++++++++++++++"
exit
fi
fi
# Starting docker service
if [ $package_manager == "yum" -o $package_manager == "apt-get" ];then
start_docker
fi
read -p 'Installation Directory [appsmith]: ' install_dir
install_dir=${install_dir:-appsmith}
mkdir -p $PWD/$install_dir
@ -159,8 +226,11 @@ elif [ $fresh_install == "Y" -o $fresh_install == "y" -o $fresh_install == "yes"
echo "Appsmith needs to create a mongo db"
mongo_host="mongo"
mongo_database="appsmith"
read -p 'Set the mongo root user: ' mongo_root_user
read -sp 'Set the mongo password: ' mongo_root_password
# We invoke functions to read the mongo credentials from the user because they MUST be non-empty
read_mongo_username
read_mongo_password
# Since the mongo was automatically setup, this must be the first time installation. Generate encryption credentials for this scenario
auto_generate_encryption="true"
fi
@ -228,6 +298,8 @@ if [[ -z $custom_domain ]]; then
NGINX_SSL_CMNT="#"
fi
echo ""
echo "Downloading the configuration templates ..."
mkdir -p template
( cd template
curl -O --silent https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/docker-compose.yml.sh
@ -238,31 +310,13 @@ curl -O --silent https://raw.githubusercontent.com/appsmithorg/appsmith/release/
curl -O --silent https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/encryption.env.sh
)
# Role - Docker
if ! is_command_present docker ;then
if [ $package_manager == "apt-get" -o $package_manager == "yum" ];then
install_docker
else
echo ""
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
# Starting docker service
if [ $package_manager == "yum" -o $package_manager == "apt-get" ];then
start_docker
fi
# Role - Folder
for directory_name in nginx certbot/conf certbot/www mongo/db
do
mkdir -p "$install_dir/data/$directory_name"
done
echo ""
echo "Generating the configuration files from the templates"
. ./template/nginx_app.conf.sh
. ./template/docker-compose.yml.sh
@ -295,15 +349,33 @@ echo "Pulling the latest container images"
sudo docker-compose pull
echo "Starting the Appsmith containers"
sudo docker-compose -f docker-compose.yml up -d --remove-orphans
# These echo statements are important for some reason. The script doesn't run successfully without them.
echo ""
echo "+++++++++++ SUCCESS ++++++++++++++++++++++"
echo "Your installation is complete. Please run the following command to ensure that all the containers are running without errors:"
echo -ne "Waiting for all containers to start. This check will timeout in $timeout seconds ...\r\c"
wait_for_containers_start 60
echo ""
echo "cd $install_dir && sudo docker-compose ps -a"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++"
echo ""
echo "Need help troubleshooting?"
echo "Join our discord server https://discord.com/invite/rBTTVJp"
echo ""
echo "Your application is running on http://localhost"
if [[ $status_code -ne 401 ]]; then
echo "+++++++++++ ERROR ++++++++++++++++++++++"
echo "The containers didn't seem to start correctly. Please run the following command to check containers that may have errored out:"
echo ""
echo "cd $install_dir && sudo docker-compose ps -a"
echo "For troubleshooting help, please reach out to us via our Discord server: https://discord.com/invite/rBTTVJp"
echo "++++++++++++++++++++++++++++++++++++++++"
echo ""
else
echo ""
echo "+++++++++++ SUCCESS ++++++++++++++++++++++"
echo "Your installation is complete. Please run the following command to ensure that all the containers are running without errors:"
echo ""
echo "cd $install_dir && sudo docker-compose ps -a"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++"
echo ""
echo "Need help troubleshooting?"
echo "Join our Discord server https://discord.com/invite/rBTTVJp"
echo ""
echo "Your application is running on http://localhost"
fi
echo -e "Peace out \U1F596"