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