Logic related to humanization of table names and other identifiers, e.g. taking an identifier like my_table and returning a human-friendly one like My Table.

There are currently two implementations of humanization logic, previously three. Which implementation is used is determined by the Setting humanization-strategy. :simple, which merely replaces underscores and dashes with spaces, and :none, which predictibly is merely an identity function that does nothing to the results.

There used to also be :advanced, which was the default until enough customers complained that we first fixed it and then the fix wasn't good enough so we removed it.

(ns metabase.models.humanization
  (:require
   [metabase.models.setting :as setting :refer [defsetting]]
   [metabase.util :as u]
   [metabase.util.humanization :as u.humanization]
   [metabase.util.i18n :refer [deferred-tru tru]]
   [metabase.util.log :as log]
   [metabase.util.malli :as mu]
   [toucan2.core :as t2]))
(declare humanization-strategy)

Convert a name, such as num_toucans, to a human-readable name, such as Num Toucans. With one arg, this uses the strategy defined by the Setting humanization-strategy. With two args, you may specify a custom strategy (intended mainly for the internal implementation):

(humanization-strategy! :simple) (name->human-readable-name "cool_toucans") ;-> "Cool Toucans" ;; this is the same as: (name->human-readable-name (humanization-strategy) "cool_toucans") ;-> "Cool Toucans" ;; specifiy a different strategy: (name->human-readable-name :none "cooltoucans") ;-> "cooltoucans"

(defn name->human-readable-name
  ([s]
   (name->human-readable-name (humanization-strategy) s))
  ([strategy s]
   (u.humanization/name->human-readable-name strategy s)))

Update all non-custom display names of all instances of model (e.g. Table or Field).

(defn- re-humanize-names!
  [old-strategy model]
  (run! (fn [{id :id, internal-name :name, display-name :display_name}]
          (let [old-strategy-display-name (name->human-readable-name old-strategy internal-name)
                new-strategy-display-name (name->human-readable-name internal-name)
                custom-display-name?      (not= old-strategy-display-name display-name)]
            (when (and (not= display-name new-strategy-display-name)
                       (not custom-display-name?))
              (log/infof "Updating display name for %s '%s': '%s' -> '%s'"
                         (name model) internal-name display-name new-strategy-display-name)
              (t2/update! model id
                          {:display_name new-strategy-display-name}))))
        (t2/reducible-select [model :id :name :display_name])))

Update the non-custom display names of all Tables & Fields in the database using new values obtained from the (obstensibly swapped implementation of) name->human-readable-name.

(mu/defn- re-humanize-table-and-field-names!
  [old-strategy :- :keyword]
  (doseq [model [:model/Table :model/Field]]
    (re-humanize-names! old-strategy model)))
(defn- set-humanization-strategy! [new-value]
  (let [new-strategy (keyword (or new-value :simple))]
    ;; check to make sure `new-strategy` is a valid strategy, or throw an Exception it is it not.
    (when-not (get-method u.humanization/name->human-readable-name new-strategy)
      (throw (IllegalArgumentException.
              (tru "Invalid humanization strategy ''{0}''. Valid strategies are: {1}"
                   new-strategy (keys (methods u.humanization/name->human-readable-name))))))
    (let [old-strategy (setting/get-value-of-type :keyword :humanization-strategy)]
      ;; ok, now set the new value
      (setting/set-value-of-type! :keyword :humanization-strategy new-value)
      ;; now rehumanize all the Tables and Fields using the new strategy.
      ;; TODO: Should we do this in a background thread because it is potentially slow?
      ;; https://github.com/metabase/metabase/issues/39406
      (log/infof "Changing Table & Field names humanization strategy from '%s' to '%s'"
                 (name old-strategy) (name new-strategy))
      (re-humanize-table-and-field-names! old-strategy))))
(defsetting ^{:added "0.28.0"} humanization-strategy
  (deferred-tru
   (str "To make table and field names more human-friendly, Metabase will replace dashes and underscores in them "
        "with spaces. We’ll capitalize each word while at it, so ‘last_visited_at’ will become ‘Last Visited At’."))
  :type       :keyword
  :default    :simple
  :visibility :settings-manager
  :export?    true
  :audit      :raw-value
  :getter     (fn []
                (let [strategy (setting/get-value-of-type :keyword :humanization-strategy)
                      valid-values (set (keys (methods u.humanization/name->human-readable-name)))
                      valid-strategy? (contains? valid-values strategy)]
                  (when (not valid-strategy?) (log/warn (u/format-color :yellow "Invalid humanization strategy '%s'. Defaulting to 'simple'" strategy)))
                  (if valid-strategy? strategy :simple)))
  :setter     set-humanization-strategy!)