Skip to content

Commit

Permalink
Enterprise settings (metabase#23441)
Browse files Browse the repository at this point in the history
* Allow for disabling settings

Disabled settings will return their default value (else nil if no
default is set). This allows us to have enterprise override settings and
use them from regular OSS code without classloaders, extra vars,
remembering to check if the feature is enabled, etc.

Motivating examples are the appearance settings. We allow
`application-font` setting to change the font of the application. This
is an enterprise feature, but anyone can post to
`api/setting/application-font` and set a new value or startup as
`MB_APPLICATION_FONT=comic-sans java -jar metabase.jar` and have the
functionality.

Same thing for application colors in static viz. The calling code just
calls `(settings/application-colors)` and uses them but doesn't check if
the enterprise settings are enabled. To do this correctly, you have to
remember to implement the following onerous procedure:

A whole namespace for a setting
```clojure
(ns metabase-enterprise.embedding.utils
  (:require [metabase.models.setting :as setting :refer [defsetting]]
            [metabase.public-settings :as public-settings]
            [metabase.public-settings.premium-features :as premium-features]
            [metabase.util.i18n :refer [deferred-tru]]))

(defsetting notification-link-base-url
  (deferred-tru "By default \"Site Url\" is used in notification links, but can be overridden.")
  :visibility :internal
  :getter (fn []
            (when (premium-features/hide-embed-branding?)
              (or (setting/get-value-of-type :string :notification-link-base-url)
                  (public-settings/site-url)))))
```

And then in the calling code you have to do the procedure to
conditionally require it and put it behind a var that can handle it
being nil:

```clojure
;; we want to load this at the top level so the Setting the namespace defines gets loaded
(def ^:private site-url*
  (or (u/ignore-exceptions
        (classloader/require 'metabase-enterprise.embedding.utils)
        (resolve 'metabase-enterprise.embedding.utils/notification-link-base-url))
      (constantly nil)))

;; and then the usage
(defn- site-url
  "Return the Notification Link Base URL if set by enterprise env var, or Site URL."
  []
  (or (site-url*) (public-settings/site-url)))
```

Far nicer to just place the following into the regular public-settings
namespace:

```clojure
(defsetting notification-link-base-url
  (deferred-tru "By default \"Site Url\" is used in notification links, but can be overridden.")
  :visibility :internal
  :enabled?    premium-features/hide-embed-branding?)
```

Then no need for a custom namespace to hold this setting, no need to
have an extra var to point to the setting else a fallback value.

Note that this feature is not required on every enterprise feature we
have. We a namespace `metabase-enterprise.sso.integrations.sso-settings`
that has 24 settings in it, all of which are enterprise features. But
these features are used in our enterprise sso offerings and are directly
referenced from the enterprise features. No need for the extra var to
point to them and the flag checks happen in other parts.

* Mark the UI/UX customization settings as requiring whitelabeling

Mark the following settings as requiring
premium-settings/enable-whitelabeling? (aka token check)

- application-name
- loading-message (override of "doing science")
- show-metabot (override of showing our friendly metabot)
- application-colors
- application-font
- application-logo-url
- application-favicon-url

Updates the helper functions for colors to use the setting rather than
feeling entitled to use a lower level `setting/get-value-of-type`. We
need the higher level api so it takes into account if its enabled or
not.

* Move notification-link-base-url into regular settings with enabled?

* Cleanup ns
  • Loading branch information
dpsutton authored Jun 23, 2022
1 parent 084fa78 commit 9c4e738
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 36 deletions.
13 changes: 0 additions & 13 deletions enterprise/backend/src/metabase_enterprise/embedding/utils.clj

This file was deleted.

22 changes: 14 additions & 8 deletions src/metabase/models/setting.clj
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,10 @@
;; called whenever setting value changes, whether from update-setting! or a cache refresh. used to handle cases
;; where a change to the cache necessitates a change to some value outside the cache, like when a change the
;; `:site-locale` setting requires a call to `java.util.Locale/setDefault`
:on-change (s/maybe clojure.lang.IFn)})
:on-change (s/maybe clojure.lang.IFn)

;; optional fn called whether to allow the getter to return a value. Useful for ensuring premium settings are not available to
:enabled? (s/maybe clojure.lang.IFn)})

(defonce ^:private registered-settings
(atom {}))
Expand Down Expand Up @@ -524,12 +527,14 @@
looks for first for a corresponding env var, then checks the cache, then returns the default value of the Setting,
if any."
[setting-definition-or-name]
(let [{:keys [cache? getter]} (resolve-setting setting-definition-or-name)
disable-cache? (not cache?)]
(if (= *disable-cache* disable-cache?)
(getter)
(binding [*disable-cache* disable-cache?]
(getter)))))
(let [{:keys [cache? getter enabled? default]} (resolve-setting setting-definition-or-name)
disable-cache? (not cache?)]
(if (or (nil? enabled?) (enabled?))
(if (= *disable-cache* disable-cache?)
(getter)
(binding [*disable-cache* disable-cache?]
(getter)))
default)))


;;; +----------------------------------------------------------------------------------------------------------------+
Expand Down Expand Up @@ -744,7 +749,8 @@
:cache? true
:database-local :never
:user-local :never
:deprecated nil}
:deprecated nil
:enabled? nil}
(dissoc setting :name :type :default)))
(s/validate SettingDefinition <>)
(validate-default-value-for-type <>)
Expand Down
17 changes: 15 additions & 2 deletions src/metabase/public_settings.clj
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,12 @@
:type :integer
:default 10)

(defsetting notification-link-base-url
(deferred-tru "By default \"Site Url\" is used in notification links, but can be overridden.")
:visibility :internal
:type :string
:enabled? premium-features/hide-embed-branding?)

(defsetting deprecation-notice-version
(deferred-tru "Metabase version for which a notice about usage of deprecated features has been shown.")
:visibility :admin)
Expand All @@ -279,17 +285,20 @@
(deferred-tru "This will replace the word \"Metabase\" wherever it appears.")
:visibility :public
:type :string
:enabled? premium-features/enable-whitelabeling?
:default "Metabase")

(defsetting loading-message
(deferred-tru "Message to show while a query is running.")
:visibility :public
:enabled? premium-features/enable-whitelabeling?
:type :keyword)

(defsetting show-metabot
(deferred-tru "Enables Metabot character on the home page")
:visibility :public
:type :boolean
:enabled? premium-features/enable-whitelabeling?
:default true)

(defsetting application-colors
Expand All @@ -298,6 +307,7 @@
"You might need to refresh your browser to see your changes take effect."))
:visibility :public
:type :json
:enabled? premium-features/enable-whitelabeling?
:default {})

(defsetting application-font
Expand All @@ -307,6 +317,7 @@
:visibility :public
:type :string
:default "Lato"
:enabled? premium-features/enable-whitelabeling?
:setter (fn [new-value]
(when new-value
(when-not (u.fonts/available-font? new-value)
Expand All @@ -316,23 +327,25 @@
(defn application-color
"The primary color, a.k.a. brand color"
[]
(or (:brand (setting/get-value-of-type :json :application-colors)) "#509EE3"))
(or (:brand (application-colors)) "#509EE3"))

(defn secondary-chart-color
"The first 'Additional chart color'"
[]
(or (:accent3 (setting/get-value-of-type :json :application-colors)) "#EF8C8C"))
(or (:accent3 (application-colors)) "#EF8C8C"))

(defsetting application-logo-url
(deferred-tru "For best results, use an SVG file with a transparent background.")
:visibility :public
:type :string
:enabled? premium-features/enable-whitelabeling?
:default "app/assets/img/logo.svg")

(defsetting application-favicon-url
(deferred-tru "The url or image that you want to use as the favicon.")
:visibility :public
:type :string
:enabled? premium-features/enable-whitelabeling?
:default "app/assets/img/favicon.ico")

(defsetting enable-password-login
Expand Down
13 changes: 2 additions & 11 deletions src/metabase/util/urls.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,12 @@
Functions for generating URLs not related to Metabase *objects* generally do not belong here, unless they are used in many places in the
codebase; one-off URL-generation functions should go in the same namespaces or modules where they are used."
(:require [metabase.plugins.classloader :as classloader]
[metabase.public-settings :as public-settings]
[metabase.util :as u]))

;; we want to load this at the top level so the Setting the namespace defines gets loaded
(def ^:private site-url*
(or (u/ignore-exceptions
(classloader/require 'metabase-enterprise.embedding.utils)
(resolve 'metabase-enterprise.embedding.utils/notification-link-base-url))
(constantly nil)))
(:require [metabase.public-settings :as public-settings]))

(defn- site-url
"Return the Notification Link Base URL if set by enterprise env var, or Site URL."
[]
(or (site-url*) (public-settings/site-url)))
(or (public-settings/notification-link-base-url) (public-settings/site-url)))

(defn pulse-url
"Return an appropriate URL for a `Pulse` with ID.
Expand Down
38 changes: 36 additions & 2 deletions test/metabase/models/setting_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,31 @@
"Test setting - this only shows up in dev (7)"
:visibility :internal)

(setting/defsetting toucan-name
(defsetting toucan-name
"Name for the Metabase Toucan mascot."
:visibility :internal)

(setting/defsetting test-setting-calculated-getter
(defsetting test-setting-calculated-getter
"Test setting - this only shows up in dev (8)"
:type :boolean
:setter :none
:getter (constantly true))

(def ^:private ^:dynamic *enabled?* false)

(defsetting test-enabled-setting-no-default
"Setting to test the `:enabled?` property of settings. This only shows up in dev."
:visibility :internal
:type :string
:enabled? (fn [] *enabled?*))

(defsetting test-enabled-setting-default
"Setting to test the `:enabled?` property of settings. This only shows up in dev."
:visibility :internal
:type :string
:default "setting-default"
:enabled? (fn [] *enabled?*))

;; ## HELPER FUNCTIONS

(defn db-fetch-setting
Expand Down Expand Up @@ -915,3 +930,22 @@
(is (= "5f7f150c"
(serdes.hash/raw-hash ["test-setting-1"])
(serdes.hash/identity-hash (db/select-one Setting :key "test-setting-1")))))))

(deftest enabled?-test
(testing "Settings can be disabled"
(testing "With no default returns nil"
(is (nil? (test-enabled-setting-no-default)))
(testing "Updating the value succeeds but still get nil because no default"
(test-enabled-setting-default! "a value")
(is (nil? (test-enabled-setting-no-default)))))
(testing "Returns default value"
(is (= "setting-default" (test-enabled-setting-default)))
(testing "Updating the value succeeds but still get default"
(test-enabled-setting-default! "non-default-value")
(is (= "setting-default" (test-enabled-setting-default))))))
(testing "When enabled get the value"
(test-enabled-setting-default! "custom")
(test-enabled-setting-no-default! "custom")
(binding [*enabled?* true]
(is (= "custom" (test-enabled-setting-default)))
(is (= "custom" (test-enabled-setting-no-default))))))

0 comments on commit 9c4e738

Please sign in to comment.