From 70ba4d5c73022af1d5113a29429a3534dbfce67e Mon Sep 17 00:00:00 2001
From: Aleksandr Lesnenko <alxnddr@users.noreply.github.com>
Date: Fri, 17 Jun 2022 23:41:56 +0400
Subject: [PATCH] override product voice (#23318)

* override product voice

* review
---
 .../metabase-enterprise/settings/selectors.js | 50 ++++++++++++-------
 .../{ => LogoUpload}/LogoUpload.jsx           | 12 ++---
 .../LogoUpload/LogoUpload.styled.tsx          | 25 ++++++++++
 .../whitelabel/components/LogoUpload/index.ts |  1 +
 .../MetabotSettingWidget.styled.tsx           | 25 ++++++++++
 .../MetabotSettingWidget.tsx                  | 36 +++++++++++++
 .../components/MetabotSettingWidget/index.ts  |  1 +
 .../metabase-enterprise/whitelabel/index.js   | 25 +++++++++-
 .../whitelabel/lib/loading-message.ts         | 17 +++++++
 .../src/metabase/admin/settings/selectors.js  |  2 +-
 frontend/src/metabase/entities/users/forms.js |  8 +--
 .../containers/HomeGreeting/HomeGreeting.tsx  |  2 +-
 frontend/src/metabase/plugins/index.ts        |  3 +-
 .../query_builder/actions/querying.js         |  4 +-
 .../components/QueryVisualization.jsx         | 12 +++--
 .../query_builder/containers/QueryBuilder.jsx |  2 +
 .../admin/settings/whitelabel.cy.spec.js      | 36 +++++++++++++
 .../permissions/sandboxes.cy.spec.js          |  2 +-
 src/metabase/public_settings.clj              | 11 ++++
 19 files changed, 236 insertions(+), 38 deletions(-)
 rename enterprise/frontend/src/metabase-enterprise/whitelabel/components/{ => LogoUpload}/LogoUpload.jsx (81%)
 create mode 100644 enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload/LogoUpload.styled.tsx
 create mode 100644 enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload/index.ts
 create mode 100644 enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/MetabotSettingWidget.styled.tsx
 create mode 100644 enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/MetabotSettingWidget.tsx
 create mode 100644 enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/index.ts
 create mode 100644 enterprise/frontend/src/metabase-enterprise/whitelabel/lib/loading-message.ts

diff --git a/enterprise/frontend/src/metabase-enterprise/settings/selectors.js b/enterprise/frontend/src/metabase-enterprise/settings/selectors.js
index fc9817867ca..55130b1c5aa 100644
--- a/enterprise/frontend/src/metabase-enterprise/settings/selectors.js
+++ b/enterprise/frontend/src/metabase-enterprise/settings/selectors.js
@@ -1,27 +1,41 @@
 import { createSelector } from "reselect";
+import { LOADING_MESSAGE_BY_SETTING } from "../whitelabel/lib/loading-message";
 
 const DEFAULT_LOGO_URL = "app/assets/img/logo.svg";
 
-export const getLogoUrl = state =>
-  state.settings.values["application-logo-url"] ||
-  state.settings.values.application_logo_url ||
-  DEFAULT_LOGO_URL;
+const hasCustomColors = settingValues => {
+  const applicationColors =
+    settingValues["application-colors"] || settingValues.application_colors;
+  return Object.keys(applicationColors || {}).length > 0;
+};
 
-const getApplicationColors = state =>
-  state.settings.values["application-colors"] ||
-  state.settings.values.application_colors;
+const getCustomLogoUrl = settingValues => {
+  return (
+    settingValues["application-logo-url"] ||
+    settingValues.application_logo_url ||
+    DEFAULT_LOGO_URL
+  );
+};
 
-export const getHasCustomColors = createSelector(
-  [getApplicationColors],
-  applicationColors => Object.keys(applicationColors || {}).length > 0,
-);
+export const hasCustomBranding = settings =>
+  hasCustomColors(settings) || getCustomLogoUrl(settings) !== DEFAULT_LOGO_URL;
 
-export const getHasCustomLogo = createSelector(
-  [getLogoUrl],
-  logoUrl => logoUrl !== DEFAULT_LOGO_URL,
-);
+export const getShowMetabot = state => state.settings.values["show-metabot"];
+
+export const getLogoUrl = state => getCustomLogoUrl(state.settings.values);
 
-export const getHasCustomBranding = createSelector(
-  [getHasCustomLogo, getHasCustomColors],
-  (hasCustomLogo, hasCustomColors) => hasCustomLogo || hasCustomColors,
+export const getHasCustomColors = state =>
+  hasCustomColors(state.settings.values);
+
+export const getHasCustomBranding = state =>
+  hasCustomBranding(state.settings.values);
+
+export const getHideMetabot = createSelector(
+  [getHasCustomBranding, getShowMetabot],
+  (hasCustomBranding, showMetabot) => hasCustomBranding || !showMetabot,
 );
+
+export const getLoadingMessage = state =>
+  LOADING_MESSAGE_BY_SETTING[
+    state.settings.values["loading-message"] ?? "doing-science"
+  ];
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload.jsx b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload/LogoUpload.jsx
similarity index 81%
rename from enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload.jsx
rename to enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload/LogoUpload.jsx
index 2a07ad43dd3..c930b0f29b6 100644
--- a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload.jsx
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload/LogoUpload.jsx
@@ -1,12 +1,13 @@
 /* eslint-disable react/prop-types */
 import React from "react";
 
-import Icon from "metabase/components/Icon";
+import Button from "metabase/core/components/Button";
 import LogoIcon from "metabase/components/LogoIcon";
-import SettingInput from "metabase/admin/settings/components/widgets/SettingInput.jsx";
-
+import SettingInput from "metabase/admin/settings/components/widgets/SettingInput";
 import { color } from "metabase/lib/colors";
 
+import { LogoFileInput } from "./LogoUpload.styled";
+
 const LogoUpload = ({ setting, onChange, ...props }) => (
   <div>
     <div className="mb1">
@@ -19,8 +20,7 @@ const LogoUpload = ({ setting, onChange, ...props }) => (
       </span>
     </div>
     {window.File && window.FileReader ? (
-      <input
-        type="file"
+      <LogoFileInput
         onChange={e => {
           if (e.target.files.length > 0) {
             const reader = new FileReader();
@@ -32,7 +32,7 @@ const LogoUpload = ({ setting, onChange, ...props }) => (
     ) : (
       <SettingInput setting={setting} onChange={onChange} {...props} />
     )}
-    <Icon name="close" onClick={() => onChange(undefined)} />
+    <Button onlyIcon icon="close" onClick={() => onChange(undefined)} />
   </div>
 );
 
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload/LogoUpload.styled.tsx b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload/LogoUpload.styled.tsx
new file mode 100644
index 00000000000..f4afcdbd149
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload/LogoUpload.styled.tsx
@@ -0,0 +1,25 @@
+import styled from "@emotion/styled";
+import { color } from "metabase/lib/colors";
+
+export const LogoFileInput = styled.input`
+  &::file-selector-button {
+    padding: 0.75rem 1rem;
+    margin-right: 1rem;
+    border-radius: 4px;
+    border: 1px solid ${color("border")};
+    background-color: ${color("white")};
+    color: ${color("text-dark")};
+    transition: 200ms;
+    cursor: pointer;
+    font-family: var(--default-font-family);
+  }
+
+  &::file-selector-button:hover {
+    color: ${color("brand")};
+    background-color: ${color("bg-light")};
+  }
+`;
+
+LogoFileInput.defaultProps = {
+  type: "file",
+};
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload/index.ts b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload/index.ts
new file mode 100644
index 00000000000..c69a9260945
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload/index.ts
@@ -0,0 +1 @@
+export { default } from "./LogoUpload";
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/MetabotSettingWidget.styled.tsx b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/MetabotSettingWidget.styled.tsx
new file mode 100644
index 00000000000..7ec741dbab3
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/MetabotSettingWidget.styled.tsx
@@ -0,0 +1,25 @@
+import styled from "@emotion/styled";
+import { color } from "metabase/lib/colors";
+
+export const MetabotSettingWidgetRoot = styled.div`
+  max-width: 530px;
+  border: 1px solid ${color("border")};
+  display: flex;
+  align-items: stretch;
+  border-radius: 0.5rem;
+`;
+
+export const MetabotContainer = styled.div`
+  padding: 2rem;
+  border-right: 1px solid ${color("border")};
+`;
+
+export const ToggleContainer = styled.div`
+  padding: 2rem 1.5rem;
+  display: flex;
+  align-items: center;
+`;
+
+export const ToggleLabel = styled.label`
+  margin-right: 2rem;
+`;
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/MetabotSettingWidget.tsx b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/MetabotSettingWidget.tsx
new file mode 100644
index 00000000000..5cacbe4a0e0
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/MetabotSettingWidget.tsx
@@ -0,0 +1,36 @@
+import React from "react";
+import { t } from "ttag";
+import { useUniqueId } from "metabase/hooks/use-unique-id";
+import MetabotLogo from "metabase/components/MetabotLogo";
+import {
+  MetabotSettingWidgetRoot,
+  MetabotContainer,
+  ToggleContainer,
+  ToggleLabel,
+} from "./MetabotSettingWidget.styled";
+import Toggle from "metabase/core/components/Toggle";
+
+const MetabotSettingWidget = ({ setting, onChange }: any) => {
+  const toggleId = useUniqueId("show-metabot-switch");
+  return (
+    <MetabotSettingWidgetRoot>
+      <MetabotContainer>
+        <MetabotLogo />
+      </MetabotContainer>
+      <ToggleContainer>
+        <ToggleLabel
+          htmlFor={toggleId}
+        >{t`Display our little friend on the homepage`}</ToggleLabel>
+        <Toggle
+          id={toggleId}
+          aria-checked={setting.value}
+          role="switch"
+          value={setting.value ?? setting.defaultValue}
+          onChange={onChange}
+        />
+      </ToggleContainer>
+    </MetabotSettingWidgetRoot>
+  );
+};
+
+export default MetabotSettingWidget;
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/index.ts b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/index.ts
new file mode 100644
index 00000000000..33bdfb72bb0
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/MetabotSettingWidget/index.ts
@@ -0,0 +1 @@
+export { default } from "./MetabotSettingWidget";
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/index.js b/enterprise/frontend/src/metabase-enterprise/whitelabel/index.js
index a9b1a8c749a..57e9d43c5ca 100644
--- a/enterprise/frontend/src/metabase-enterprise/whitelabel/index.js
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/index.js
@@ -12,17 +12,21 @@ import { hasPremiumFeature } from "metabase-enterprise/settings";
 import {
   getHasCustomBranding,
   getHasCustomColors,
-  getHasCustomLogo,
+  getHideMetabot,
+  hasCustomBranding,
+  getLoadingMessage,
 } from "metabase-enterprise/settings/selectors";
 import MetabaseSettings from "metabase/lib/settings";
 
 import ColorSettingsWidget from "./components/ColorSettingsWidget";
+import MetabotSettingWidget from "./components/MetabotSettingWidget";
 import LogoUpload from "./components/LogoUpload";
 import LogoIcon from "./components/LogoIcon";
 import {
   updateColors,
   enabledApplicationNameReplacement,
 } from "./lib/whitelabel";
+import { getLoadingMessageOptions } from "./lib/loading-message";
 
 if (hasPremiumFeature("whitelabel")) {
   PLUGIN_LANDING_PAGE.push(() => MetabaseSettings.get("landing-page"));
@@ -72,6 +76,22 @@ if (hasPremiumFeature("whitelabel")) {
           type: "string",
           placeholder: "/",
         },
+        {
+          key: "loading-message",
+          display_name: t`Loading message`,
+          type: "select",
+          options: getLoadingMessageOptions(),
+          defaultValue: "doing-science",
+        },
+        {
+          key: "show-metabot",
+          display_name: t`Metabot`,
+          description: null,
+          type: "boolean",
+          widget: MetabotSettingWidget,
+          defaultValue: true,
+          getHidden: settings => hasCustomBranding(settings),
+        },
       ],
     },
     ...sections,
@@ -88,6 +108,7 @@ if (hasPremiumFeature("whitelabel")) {
 }
 
 // these selectors control whitelabeling UI
-PLUGIN_SELECTORS.getHasCustomLogo = getHasCustomLogo;
 PLUGIN_SELECTORS.getHasCustomColors = getHasCustomColors;
 PLUGIN_SELECTORS.getHasCustomBranding = getHasCustomBranding;
+PLUGIN_SELECTORS.getHideMetabot = getHideMetabot;
+PLUGIN_SELECTORS.getLoadingMessage = getLoadingMessage;
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/lib/loading-message.ts b/enterprise/frontend/src/metabase-enterprise/whitelabel/lib/loading-message.ts
new file mode 100644
index 00000000000..53ff3801d7f
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/lib/loading-message.ts
@@ -0,0 +1,17 @@
+import { t } from "ttag";
+
+export const LOADING_MESSAGE_BY_SETTING = {
+  "doing-science": t`Doing science...`,
+  "running-query": t`Running query...`,
+  "loading-results": t`Loading results...`,
+};
+
+type LoadingMessageSettingValue = keyof typeof LOADING_MESSAGE_BY_SETTING;
+
+export const getLoadingMessageOptions = () =>
+  Object.keys(LOADING_MESSAGE_BY_SETTING).map(key => {
+    return {
+      value: key,
+      name: LOADING_MESSAGE_BY_SETTING[key as LoadingMessageSettingValue],
+    };
+  });
diff --git a/frontend/src/metabase/admin/settings/selectors.js b/frontend/src/metabase/admin/settings/selectors.js
index 6bfbd1d854c..d4751a1940d 100644
--- a/frontend/src/metabase/admin/settings/selectors.js
+++ b/frontend/src/metabase/admin/settings/selectors.js
@@ -176,7 +176,7 @@ const SECTIONS = updateSectionsWithPlugins({
         key: "email-smtp-username",
         display_name: t`SMTP Username`,
         description: null,
-        placeholder: "youlooknicetoday",
+        placeholder: "nicetoseeyou",
         type: "string",
       },
       {
diff --git a/frontend/src/metabase/entities/users/forms.js b/frontend/src/metabase/entities/users/forms.js
index 3eadd7802b8..47b30b04742 100644
--- a/frontend/src/metabase/entities/users/forms.js
+++ b/frontend/src/metabase/entities/users/forms.js
@@ -28,7 +28,7 @@ const getNameFields = () => [
 const getEmailField = () => ({
   name: "email",
   title: t`Email`,
-  placeholder: "youlooknicetoday@email.com",
+  placeholder: "nicetoseeyou@email.com",
   validate: validate.required().email(),
 });
 
@@ -104,7 +104,7 @@ export default {
       {
         name: "email",
         title: t`Email`,
-        placeholder: "youlooknicetoday@email.com",
+        placeholder: "nicetoseeyou@email.com",
         validate: email => {
           if (!email) {
             return t`required`;
@@ -127,7 +127,7 @@ export default {
           name: "username",
           type: ldap ? "input" : "email",
           title: ldap ? t`Username or email address` : t`Email address`,
-          placeholder: t`youlooknicetoday@email.com`,
+          placeholder: t`nicetoseeyou@email.com`,
           validate: ldap ? validate.required() : validate.required().email(),
           autoFocus: true,
         },
@@ -179,7 +179,7 @@ export default {
     fields: [
       {
         name: "email",
-        placeholder: "youlooknicetoday@email.com",
+        placeholder: "nicetoseeyou@email.com",
         autoFocus: true,
         validate: validate.required().email(),
       },
diff --git a/frontend/src/metabase/home/homepage/containers/HomeGreeting/HomeGreeting.tsx b/frontend/src/metabase/home/homepage/containers/HomeGreeting/HomeGreeting.tsx
index aa925b4b327..99f529c8930 100644
--- a/frontend/src/metabase/home/homepage/containers/HomeGreeting/HomeGreeting.tsx
+++ b/frontend/src/metabase/home/homepage/containers/HomeGreeting/HomeGreeting.tsx
@@ -6,7 +6,7 @@ import HomeGreeting from "../../components/HomeGreeting";
 
 const mapStateToProps = (state: State) => ({
   user: getUser(state),
-  showLogo: !PLUGIN_SELECTORS.getHasCustomBranding(state),
+  showLogo: !PLUGIN_SELECTORS.getHideMetabot(state),
 });
 
 export default connect(mapStateToProps)(HomeGreeting);
diff --git a/frontend/src/metabase/plugins/index.ts b/frontend/src/metabase/plugins/index.ts
index f6dce44f35a..87cbdb4d954 100644
--- a/frontend/src/metabase/plugins/index.ts
+++ b/frontend/src/metabase/plugins/index.ts
@@ -73,10 +73,11 @@ export const PLUGIN_SHOW_CHANGE_PASSWORD_CONDITIONS = [];
 
 // selectors that customize behavior between app versions
 export const PLUGIN_SELECTORS = {
-  getHasCustomLogo: (state: State) => false,
   getHasCustomColors: (state: State) => false,
   getHasCustomBranding: (state: State) => false,
   canWhitelabel: (state: State) => false,
+  getHideMetabot: (state: State) => false,
+  getLoadingMessage: (state: State) => t`Doing science...`,
 };
 
 export const PLUGIN_FORM_WIDGETS = {};
diff --git a/frontend/src/metabase/query_builder/actions/querying.js b/frontend/src/metabase/query_builder/actions/querying.js
index 307502d8bac..c55407cc042 100644
--- a/frontend/src/metabase/query_builder/actions/querying.js
+++ b/frontend/src/metabase/query_builder/actions/querying.js
@@ -4,6 +4,7 @@ import { t } from "ttag";
 
 import { createAction } from "redux-actions";
 
+import { PLUGIN_SELECTORS } from "metabase/plugins";
 import * as MetabaseAnalytics from "metabase/lib/analytics";
 import { isAdHocModelQuestion } from "metabase/lib/data-modeling/utils";
 import { startTimer } from "metabase/lib/performance";
@@ -160,8 +161,9 @@ export const runQuestionQuery = ({
 const loadStartUIControls = createThunkAction(
   LOAD_START_UI_CONTROLS,
   () => (dispatch, getState) => {
+    const loadingMessage = PLUGIN_SELECTORS.getLoadingMessage(getState());
     const title = {
-      onceQueryIsRun: t`Doing Science...`,
+      onceQueryIsRun: loadingMessage,
       ifQueryTakesLong: t`Still Here...`,
     };
 
diff --git a/frontend/src/metabase/query_builder/components/QueryVisualization.jsx b/frontend/src/metabase/query_builder/components/QueryVisualization.jsx
index 9890bd20500..1a618778fe9 100644
--- a/frontend/src/metabase/query_builder/components/QueryVisualization.jsx
+++ b/frontend/src/metabase/query_builder/components/QueryVisualization.jsx
@@ -59,11 +59,17 @@ export default class QueryVisualization extends Component {
       isResultDirty,
       isNativeEditorOpen,
       result,
+      loadingMessage,
     } = this.props;
 
     return (
       <div className={cx(className, "relative stacking-context")}>
-        {isRunning ? <VisualizationRunningState className="spread z2" /> : null}
+        {isRunning ? (
+          <VisualizationRunningState
+            className="spread z2"
+            loadingMessage={loadingMessage}
+          />
+        ) : null}
         <VisualizationDirtyState
           {...this.props}
           hidden={!isResultDirty || isRunning || isNativeEditorOpen}
@@ -112,7 +118,7 @@ export const VisualizationEmptyState = ({ className }) => (
   </div>
 );
 
-export const VisualizationRunningState = ({ className }) => (
+export const VisualizationRunningState = ({ className, loadingMessage }) => (
   <div
     className={cx(
       className,
@@ -121,7 +127,7 @@ export const VisualizationRunningState = ({ className }) => (
   >
     <LoadingSpinner />
     <h2 className="Loading-message text-brand text-uppercase my3">
-      {t`Doing science`}...
+      {loadingMessage}
     </h2>
   </div>
 );
diff --git a/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx b/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx
index d3f15a4772e..a818c82fc35 100644
--- a/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx
+++ b/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx
@@ -11,6 +11,7 @@ import { push } from "react-router-redux";
 import { t } from "ttag";
 import _ from "underscore";
 
+import { PLUGIN_SELECTORS } from "metabase/plugins";
 import Bookmark from "metabase/entities/bookmarks";
 import Collections from "metabase/entities/collections";
 import Timelines from "metabase/entities/timelines";
@@ -199,6 +200,7 @@ const mapStateToProps = (state, props) => {
     documentTitle: getDocumentTitle(state),
     pageFavicon: getPageFavicon(state),
     isLoadingComplete: getIsLoadingComplete(state),
+    loadingMessage: PLUGIN_SELECTORS.getLoadingMessage(state),
   };
 };
 
diff --git a/frontend/test/metabase/scenarios/admin/settings/whitelabel.cy.spec.js b/frontend/test/metabase/scenarios/admin/settings/whitelabel.cy.spec.js
index f8fec93c594..f13fe775aec 100644
--- a/frontend/test/metabase/scenarios/admin/settings/whitelabel.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/settings/whitelabel.cy.spec.js
@@ -122,6 +122,36 @@ describeEE("formatting > whitelabel", () => {
     });
   });
 
+  describe("loading message", () => {
+    it("should update loading message", () => {
+      cy.visit("/question/1");
+      cy.findByText("Doing science...");
+
+      const runningQueryMessage = "Running query...";
+      changeLoadingMessage(runningQueryMessage);
+      cy.visit("/question/1");
+      cy.findByText(runningQueryMessage);
+
+      const loadingResultsMessage = "Loading results...";
+      changeLoadingMessage(loadingResultsMessage);
+      cy.visit("/question/1");
+      cy.findByText(loadingResultsMessage);
+    });
+  });
+
+  describe("metabot", () => {
+    it("should toggle metabot visibility", () => {
+      cy.visit("/");
+      cy.findByAltText("Metabot");
+
+      cy.visit("/admin/settings/whitelabel");
+      cy.findByText("Display our little friend on the homepage").click();
+
+      cy.visit("/");
+      cy.findByAltText("Metabot").should("not.exist");
+    });
+  });
+
   describe("font", () => {
     const font = "Open Sans";
     beforeEach(() => {
@@ -138,6 +168,12 @@ describeEE("formatting > whitelabel", () => {
   });
 });
 
+function changeLoadingMessage(message) {
+  cy.visit("/admin/settings/whitelabel");
+  cy.findByTestId("loading-message-select-button").click();
+  cy.findByText(message).click();
+}
+
 function setApplicationFontTo(font) {
   cy.request("PUT", "/api/setting/application-font", {
     value: font,
diff --git a/frontend/test/metabase/scenarios/permissions/sandboxes.cy.spec.js b/frontend/test/metabase/scenarios/permissions/sandboxes.cy.spec.js
index 7a4d26aa7b7..e740fdd0e74 100644
--- a/frontend/test/metabase/scenarios/permissions/sandboxes.cy.spec.js
+++ b/frontend/test/metabase/scenarios/permissions/sandboxes.cy.spec.js
@@ -56,7 +56,7 @@ describeEE("formatting > sandboxes", () => {
       cy.findByText("Invite someone").click();
       cy.findByPlaceholderText("Johnny").type("John");
       cy.findByPlaceholderText("Appleseed").type("Smith");
-      cy.findByPlaceholderText("youlooknicetoday@email.com").type(
+      cy.findByPlaceholderText("nicetoseeyou@email.com").type(
         "john@smith.test",
       );
       cy.findByText("Add an attribute").click();
diff --git a/src/metabase/public_settings.clj b/src/metabase/public_settings.clj
index 3486eddb0f6..d68817b9f3c 100644
--- a/src/metabase/public_settings.clj
+++ b/src/metabase/public_settings.clj
@@ -281,6 +281,17 @@
   :type       :string
   :default    "Metabase")
 
+(defsetting loading-message
+  (deferred-tru "Message to show while a query is running.")
+  :visibility :public
+  :type       :keyword)
+
+(defsetting show-metabot
+  (deferred-tru "Enables Metabot character on the home page")
+  :visibility :public
+  :type       :boolean
+  :default    true)
+
 (defsetting application-colors
   (deferred-tru
    (str "These are the primary colors used in charts and throughout Metabase. "
-- 
GitLab