Skip to content
Snippets Groups Projects
Unverified Commit a39a1f6d authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Add setup invitation validation (#19441)

parent ae5442a4
Branches
Tags
No related merge requests found
......@@ -2,11 +2,12 @@ import _ from "underscore";
import { t } from "ttag";
import MetabaseSettings from "metabase/lib/settings";
import MetabaseUtils from "metabase/lib/utils";
import { PLUGIN_ADMIN_USER_FORM_FIELDS } from "metabase/plugins";
import validate from "metabase/lib/validate";
import FormGroupsWidget from "metabase/components/form/widgets/FormGroupsWidget";
const DETAILS_FORM_FIELDS = () => [
const NAME_FIELDS = [
{
name: "first_name",
title: t`First name`,
......@@ -20,14 +21,15 @@ const DETAILS_FORM_FIELDS = () => [
placeholder: "Appleseed",
validate: validate.required().maxLength(100),
},
{
name: "email",
title: t`Email`,
placeholder: "youlooknicetoday@email.com",
validate: validate.required().email(),
},
];
const EMAIL_FIELD = {
name: "email",
title: t`Email`,
placeholder: "youlooknicetoday@email.com",
validate: validate.required().email(),
};
const LOCALE_FIELD = {
name: "locale",
title: t`Language`,
......@@ -41,7 +43,7 @@ const LOCALE_FIELD = {
].map(([code, name]) => ({ name, value: code })),
};
const PASSWORD_FORM_FIELDS = () => [
const PASSWORD_FORM_FIELDS = [
{
name: "password",
title: t`Create a password`,
......@@ -54,16 +56,21 @@ const PASSWORD_FORM_FIELDS = () => [
title: t`Confirm your password`,
type: "password",
placeholder: t`Shhh... but one more time so we get it right`,
validate: (password_confirm, { values: { password } = {} }) =>
(!password_confirm && t`required`) ||
(password_confirm !== password && t`passwords do not match`),
validate: (password_confirm, { values: { password } = {} }) => {
if (!password_confirm) {
return t`required`;
} else if (password_confirm !== password) {
return t`passwords do not match`;
}
},
},
];
export default {
admin: {
fields: [
...DETAILS_FORM_FIELDS(),
...NAME_FIELDS,
EMAIL_FIELD,
{
name: "group_ids",
title: t`Groups`,
......@@ -73,19 +80,39 @@ export default {
],
},
user: {
fields: [...DETAILS_FORM_FIELDS(), LOCALE_FIELD],
fields: [...NAME_FIELDS, EMAIL_FIELD, LOCALE_FIELD],
disablePristineSubmit: true,
},
setup: () => ({
setup: {
fields: [
...DETAILS_FORM_FIELDS(),
...NAME_FIELDS,
EMAIL_FIELD,
{
name: "site_name",
title: t`Company or team name`,
placeholder: t`Department of Awesome`,
validate: validate.required(),
},
...PASSWORD_FORM_FIELDS(),
...PASSWORD_FORM_FIELDS,
],
},
setup_invite: user => ({
fields: [
...NAME_FIELDS,
{
name: "email",
title: t`Email`,
placeholder: "youlooknicetoday@email.com",
validate: email => {
if (!email) {
return t`required`;
} else if (!MetabaseUtils.isEmail(email)) {
return t`must be a valid email address`;
} else if (email === user.email) {
return t`must be different from the email address you used in setup`;
}
},
},
],
}),
password: {
......@@ -97,10 +124,10 @@ export default {
placeholder: t`Shhh...`,
validate: validate.required(),
},
...PASSWORD_FORM_FIELDS(),
...PASSWORD_FORM_FIELDS,
],
},
password_reset: {
fields: [...PASSWORD_FORM_FIELDS()],
fields: [...PASSWORD_FORM_FIELDS],
},
};
......@@ -13,11 +13,12 @@ import {
StepLink,
} from "./DatabaseStep.styled";
import { FormProps } from "./types";
import { DatabaseInfo, InviteInfo } from "../../types";
import { DatabaseInfo, InviteInfo, UserInfo } from "../../types";
export interface DatabaseStepProps {
engine?: string;
user?: UserInfo;
database?: DatabaseInfo;
engine?: string;
invite?: InviteInfo;
isEmailConfigured: boolean;
isStepActive: boolean;
......@@ -31,8 +32,9 @@ export interface DatabaseStepProps {
}
const DatabaseStep = ({
engine,
user,
database,
engine,
invite,
isEmailConfigured,
isStepActive,
......@@ -74,8 +76,8 @@ const DatabaseStep = ({
<div>{t`Not ready? Skip and play around with our Sample Dataset.`}</div>
</StepDescription>
<DatabaseForm
engine={engine}
database={database}
engine={engine}
onSubmit={onDatabaseSubmit}
/>
<StepActions>
......@@ -86,7 +88,7 @@ const DatabaseStep = ({
title={t`Need help connecting to your data?`}
description={t`Invite a teammate. We’ll make them an admin so they can configure your database. You can always change this later on.`}
>
<InviteForm invite={invite} onSubmit={onInviteSubmit} />
<InviteForm user={user} invite={invite} onSubmit={onInviteSubmit} />
</SetupSection>
)}
</ActiveStep>
......@@ -94,8 +96,8 @@ const DatabaseStep = ({
};
interface DatabaseFormProps {
engine?: string;
database?: DatabaseInfo;
engine?: string;
onSubmit: (database: DatabaseInfo) => void;
}
......@@ -132,14 +134,19 @@ const DatabaseForm = ({
};
interface InviteFormProps {
user?: UserInfo;
invite?: InviteInfo;
onSubmit: (invite: InviteInfo) => void;
}
const InviteForm = ({ invite, onSubmit }: InviteFormProps): JSX.Element => {
const InviteForm = ({
user,
invite,
onSubmit,
}: InviteFormProps): JSX.Element => {
return (
<Users.Form
form={Users.forms.setup_invite}
form={Users.forms.setup_invite(user)}
user={invite}
onSubmit={onSubmit}
>
......
......@@ -41,6 +41,7 @@ const PreferencesStep = ({
await onStepSubmit(isTrackingAllowed);
} catch (error) {
setErrorMessage(getSubmitError(error));
throw error;
}
};
......@@ -87,8 +88,8 @@ const PreferencesStep = ({
</StepInfoList>
)}
<ActionButton
normalText={t`Next`}
activeText={t`Next`}
normalText={t`Finish`}
activeText={t`Finishing…`}
failedText={t`Failed`}
successText={t`Success`}
primary
......
......@@ -34,7 +34,7 @@ describe("PreferencesStep", () => {
});
render(<PreferencesStep {...props} />);
userEvent.click(screen.getByText("Next"));
userEvent.click(screen.getByText("Finish"));
expect(await screen.findByText("An error occurred")).toBeInTheDocument();
});
......
......@@ -80,7 +80,7 @@ const UserForm = ({ user, onSubmit, onPasswordChange }: UserFormProps) => {
return (
<UserFormRoot
form={Users.forms.setup()}
form={Users.forms.setup}
user={user}
asyncValidate={handleAsyncValidate}
asyncBlurFields={["password"]}
......
......@@ -15,12 +15,14 @@ import {
isSetupCompleted,
getDatabaseEngine,
getInvite,
getUser,
} from "../../selectors";
import { DatabaseInfo, InviteInfo } from "../../types";
const mapStateToProps = (state: any) => ({
engine: getDatabaseEngine(state),
user: getUser(state),
database: getDatabase(state),
engine: getDatabaseEngine(state),
invite: getInvite(state),
isEmailConfigured: Settings.isEmailConfigured(),
isStepActive: isStepActive(state, DATABASE_STEP),
......
......@@ -154,7 +154,7 @@ describe("scenarios > setup", () => {
cy.findByText("All collection is completely anonymous.").should(
"not.exist",
);
cy.findByText("Next").click();
cy.findByText("Finish").click();
// ==================
// Finish & Subscribe
......
......@@ -63,7 +63,7 @@ describe("metabase-smoketest > admin", () => {
cy.findByText("All collection is completely anonymous.").should(
"not.exist",
);
cy.findByText("Next").click();
cy.findByText("Finish").click();
// Finish & Subscribe
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment