Skip to content
Snippets Groups Projects
Unverified Commit 635ad3a4 authored by Nemanja Glumac's avatar Nemanja Glumac Committed by GitHub
Browse files

Pre fill non-sensitive user information during setup for hosted instances (#48019)

* Pre-fill cloud user info from search params

* Autofocus password field for hosted instances

* Add tests

* Parse setup user info and store it immediatelly

* Autofocus password input field for hosted instances

* Add e2e reproduction

* Fix the user info

* Fix unit test
parent dddf499a
No related branches found
No related tags found
No related merge requests found
...@@ -8,9 +8,12 @@ import { ...@@ -8,9 +8,12 @@ import {
expectNoBadSnowplowEvents, expectNoBadSnowplowEvents,
isEE, isEE,
main, main,
mockSessionProperty,
onlyOnEE,
popover, popover,
resetSnowplow, resetSnowplow,
restore, restore,
setTokenFeatures,
} from "e2e/support/helpers"; } from "e2e/support/helpers";
import { SUBSCRIBE_URL } from "metabase/setup/constants"; import { SUBSCRIBE_URL } from "metabase/setup/constants";
...@@ -233,6 +236,29 @@ describe("scenarios > setup", () => { ...@@ -233,6 +236,29 @@ describe("scenarios > setup", () => {
}); });
}); });
it("should pre-fill user info for hosted instances (infra-frontend#1109)", () => {
onlyOnEE();
setTokenFeatures("none");
mockSessionProperty("is-hosted?", true);
cy.visit(
"/setup?first_name=John&last_name=Doe&email=john@doe.test&site_name=Doe%20Unlimited",
);
skipWelcomePage();
selectPreferredLanguageAndContinue();
cy.findByTestId("setup-forms").within(() => {
cy.findByDisplayValue("John").should("exist");
cy.findByDisplayValue("Doe").should("exist");
cy.findByDisplayValue("john@doe.test").should("exist");
cy.findByDisplayValue("Doe Unlimited").should("exist");
cy.findByLabelText("Create a password")
.should("be.focused")
.and("be.empty");
});
});
it("should allow you to connect a db during setup", () => { it("should allow you to connect a db during setup", () => {
const dbName = "SQLite db"; const dbName = "SQLite db";
......
...@@ -32,12 +32,14 @@ const USER_SCHEMA = Yup.object({ ...@@ -32,12 +32,14 @@ const USER_SCHEMA = Yup.object({
interface UserFormProps { interface UserFormProps {
user?: UserInfo; user?: UserInfo;
isHosted: boolean;
onValidatePassword: (password: string) => Promise<string | undefined>; onValidatePassword: (password: string) => Promise<string | undefined>;
onSubmit: (user: UserInfo) => Promise<void>; onSubmit: (user: UserInfo) => Promise<void>;
} }
export const UserForm = ({ export const UserForm = ({
user, user,
isHosted,
onValidatePassword, onValidatePassword,
onSubmit, onSubmit,
}: UserFormProps) => { }: UserFormProps) => {
...@@ -66,7 +68,7 @@ export const UserForm = ({ ...@@ -66,7 +68,7 @@ export const UserForm = ({
title={t`First name`} title={t`First name`}
placeholder={t`Johnny`} placeholder={t`Johnny`}
nullable nullable
autoFocus autoFocus={!isHosted}
/> />
<FormInput <FormInput
name="last_name" name="last_name"
...@@ -91,6 +93,10 @@ export const UserForm = ({ ...@@ -91,6 +93,10 @@ export const UserForm = ({
type="password" type="password"
title={t`Create a password`} title={t`Create a password`}
placeholder={t`Shhh...`} placeholder={t`Shhh...`}
// Hosted instances always pass user information in the URLSearchParams
// during the initial setup. Password is the first empty field
// so it makes sense to focus on it.
autoFocus={isHosted && initialValues.site_name !== ""}
/> />
<FormInput <FormInput
name="password_confirm" name="password_confirm"
......
...@@ -16,6 +16,7 @@ import { StepDescription } from "./UserStep.styled"; ...@@ -16,6 +16,7 @@ import { StepDescription } from "./UserStep.styled";
export const UserStep = ({ stepLabel }: NumberedStepProps): JSX.Element => { export const UserStep = ({ stepLabel }: NumberedStepProps): JSX.Element => {
const { isStepActive, isStepCompleted } = useStep("user_info"); const { isStepActive, isStepCompleted } = useStep("user_info");
const user = useSelector(getUser); const user = useSelector(getUser);
const isHosted = useSelector(getIsHosted); const isHosted = useSelector(getIsHosted);
...@@ -45,6 +46,7 @@ export const UserStep = ({ stepLabel }: NumberedStepProps): JSX.Element => { ...@@ -45,6 +46,7 @@ export const UserStep = ({ stepLabel }: NumberedStepProps): JSX.Element => {
)} )}
<UserForm <UserForm
user={user} user={user}
isHosted={isHosted}
onValidatePassword={validatePassword} onValidatePassword={validatePassword}
onSubmit={handleSubmit} onSubmit={handleSubmit}
/> />
......
import { mockSettings } from "__support__/settings";
import { renderWithProviders, screen } from "__support__/ui"; import { renderWithProviders, screen } from "__support__/ui";
import type { SetupStep } from "metabase/setup/types"; import type { SetupStep } from "metabase/setup/types";
import type { UserInfo } from "metabase-types/store"; import type { UserInfo } from "metabase-types/store";
...@@ -12,10 +13,16 @@ import { UserStep } from "./UserStep"; ...@@ -12,10 +13,16 @@ import { UserStep } from "./UserStep";
interface SetupOpts { interface SetupOpts {
step?: SetupStep; step?: SetupStep;
user?: UserInfo; user?: UserInfo;
isHosted?: boolean;
} }
const setup = ({ step = "user_info", user }: SetupOpts = {}) => { const setup = ({
step = "user_info",
user,
isHosted = false,
}: SetupOpts = {}) => {
const state = createMockState({ const state = createMockState({
settings: mockSettings({ "is-hosted?": isHosted }),
setup: createMockSetupState({ setup: createMockSetupState({
step, step,
user, user,
...@@ -32,6 +39,30 @@ describe("UserStep", () => { ...@@ -32,6 +39,30 @@ describe("UserStep", () => {
expect(screen.getByText("What should we call you?")).toBeInTheDocument(); expect(screen.getByText("What should we call you?")).toBeInTheDocument();
}); });
it("should autofocus the first name input field", () => {
setup({ step: "user_info" });
expect(screen.getByLabelText("First name")).toHaveFocus();
});
it("should autofocus the password input field for hosted instances", () => {
const user = createMockUserInfo();
setup({ step: "user_info", isHosted: true, user });
expect(screen.getByLabelText("Create a password")).toHaveFocus();
});
it("should pre-fill the user information if provided", () => {
const user = createMockUserInfo();
setup({ step: "user_info", user });
Object.values(user)
.filter(v => v.length > 0)
.forEach(v => {
expect(screen.getByDisplayValue(v)).toBeInTheDocument();
});
});
it("should render in completed state", () => { it("should render in completed state", () => {
setup({ setup({
step: "db_connection", step: "db_connection",
......
...@@ -17,15 +17,33 @@ import { ...@@ -17,15 +17,33 @@ import {
updateTracking, updateTracking,
} from "./actions"; } from "./actions";
const getUserFromQueryParams = () => {
const params = new URLSearchParams(window.location.search);
const getParam = (key: string, defaultValue = "") =>
params.get(key) || defaultValue;
return {
first_name: getParam("first_name") || null,
last_name: getParam("last_name") || null,
email: getParam("email"),
site_name: getParam("site_name"),
password: "",
password_confirm: "",
};
};
const initialState: SetupState = { const initialState: SetupState = {
step: "welcome", step: "welcome",
isLocaleLoaded: false, isLocaleLoaded: false,
isTrackingAllowed: true, isTrackingAllowed: true,
user: getUserFromQueryParams(),
}; };
export const reducer = createReducer(initialState, builder => { export const reducer = createReducer(initialState, builder => {
builder.addCase(loadUserDefaults.fulfilled, (state, { payload: user }) => { builder.addCase(loadUserDefaults.fulfilled, (state, { payload: user }) => {
state.user = user; if (user) {
state.user = user;
}
}); });
builder.addCase( builder.addCase(
loadLocaleDefaults.fulfilled, loadLocaleDefaults.fulfilled,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment