(ns metabase.xrays.automagic-dashboards.names (:require [clojure.string :as str] [java-time.api :as t] [metabase.legacy-mbql.normalize :as mbql.normalize] [metabase.lib.util.match :as lib.util.match] [metabase.query-processor.util :as qp.util] [metabase.util.date-2 :as u.date] [metabase.util.i18n :refer [deferred-tru tru]] [metabase.xrays.automagic-dashboards.util :as magic.util] [toucan2.core :as t2])) | |
TODO - rename "minumum" to "minimum". Note that there are internationalization string implications here so make sure to do a thorough find and replace on this. | (def ^:private op->name
{:sum (deferred-tru "sum")
:avg (deferred-tru "average")
:min (deferred-tru "minumum")
:max (deferred-tru "maximum")
:count (deferred-tru "number")
:distinct (deferred-tru "distinct count")
:stddev (deferred-tru "standard deviation")
:cum-count (deferred-tru "cumulative count")
:cum-sum (deferred-tru "cumulative sum")}) |
Return the name of the metric or name by describing it. | (defn metric-name
[[op & args :as metric]]
(cond
(magic.util/adhoc-metric? metric) (-> op qp.util/normalize-token op->name)
(magic.util/saved-metric? metric) (->> args first (t2/select-one :model/LegacyMetric :id) :name)
:else (second args))) |
Join a sequence as [1 2 3 4] to "1, 2, 3 and 4" | (defn- join-enumeration
[xs]
(if (next xs)
(tru "{0} and {1}" (str/join ", " (butlast xs)) (last xs))
(first xs))) |
Return the (display) name of the source of a given root object. | (def ^{:arglists '([root])} source-name
(comp (some-fn :display_name :name) :source)) |
Return a description for the metric. | (defn metric->description
[root aggregation-clause]
(join-enumeration
(for [metric (if (sequential? (first aggregation-clause))
aggregation-clause
[aggregation-clause])]
(if (magic.util/adhoc-metric? metric)
(tru "{0} of {1}" (metric-name metric) (or (some->> metric
second
(magic.util/->field root)
:display_name)
(source-name root)))
(metric-name metric))))) |
Generate a description for the question. | (defn question-description
[root question]
(let [aggregations (->> (get-in question [:dataset_query :query :aggregation])
(metric->description root))
dimensions (->> (get-in question [:dataset_query :query :breakout])
(mapcat magic.util/collect-field-references)
(map (comp :display_name
(partial magic.util/->field root)))
join-enumeration)]
(if dimensions
(tru "{0} by {1}" aggregations dimensions)
aggregations))) |
(defmulti ^:private humanize-filter-value
{:arglists '([fieldset [op & args]])}
(fn [_ [op & _args]]
(qp.util/normalize-token op))) | |
(def ^:private unit-name (comp {:minute-of-hour (deferred-tru "minute")
:hour-of-day (deferred-tru "hour")
:day-of-week (deferred-tru "day of week")
:day-of-month (deferred-tru "day of month")
:day-of-year (deferred-tru "day of year")
:week-of-year (deferred-tru "week")
:month-of-year (deferred-tru "month")
:quarter-of-year (deferred-tru "quarter")
:year (deferred-tru "year")}
qp.util/normalize-token)) | |
Turn a field reference into a field. | (defn item-reference->field
[root [item-type :as item-reference]]
(case item-type
(:field "field") (let [normalized-field-reference (mbql.normalize/normalize item-reference)
temporal-unit (lib.util.match/match-one normalized-field-reference
[:field _ (opts :guard :temporal-unit)]
(:temporal-unit opts))
{:keys [display_name] :as field-record} (cond-> (->> normalized-field-reference
magic.util/collect-field-references
first
(magic.util/->field root))
temporal-unit
(assoc :unit temporal-unit))
item-name (cond->> display_name
(some-> temporal-unit u.date/extract-units)
(tru "{0} of {1}" (unit-name temporal-unit)))]
(assoc field-record :item-name item-name))
(:expression "expression") {:item-name (second item-reference)}
{:item-name "item"})) |
Determine the right name to display from an individual humanized item. | (defn item-name
([root [field-type potential-name :as field-reference]]
(case field-type
(:field "field") (->> field-reference (item-reference->field root) item-name)
(:expression "expression") potential-name
"item"))
([{:keys [display_name unit] :as _field}]
(cond->> display_name
(some-> unit u.date/extract-units) (tru "{0} of {1}" (unit-name unit))))) |
Add appropriate pluralization suffixes for integer numbers. | (defn pluralize
[x]
;; the `int` cast here is to fix performance warnings if `*warn-on-reflection*` is enabled
(case (int (mod x 10))
1 (tru "{0}st" x)
2 (tru "{0}nd" x)
3 (tru "{0}rd" x)
(tru "{0}th" x))) |
Convert a time data type into a human friendly string. | (defn humanize-datetime
[t-str unit]
(let [dt (u.date/parse t-str)]
(case unit
:second (tru "at {0}" (t/format "h:mm:ss a, MMMM d, YYYY" dt))
:minute (tru "at {0}" (t/format "h:mm a, MMMM d, YYYY" dt))
:hour (tru "at {0}" (t/format "h a, MMMM d, YYYY" dt))
:day (tru "on {0}" (t/format "MMMM d, YYYY" dt))
:week (tru "in {0} week - {1}"
(pluralize (u.date/extract dt :week-of-year))
(str (u.date/extract dt :year)))
:month (tru "in {0}" (t/format "MMMM YYYY" dt))
:quarter (tru "in Q{0} - {1}"
(u.date/extract dt :quarter-of-year)
(str (u.date/extract dt :year)))
:year (t/format "YYYY" dt)
:day-of-week (t/format "EEEE" dt)
:hour-of-day (tru "at {0}" (t/format "h a" dt))
:month-of-year (t/format "MMMM" dt)
:quarter-of-year (tru "Q{0}" (u.date/extract dt :quarter-of-year))
(:minute-of-hour
:day-of-month
:day-of-year
:week-of-year) (u.date/extract dt unit)))) |
(defmethod humanize-filter-value :=
[root [_ field-reference value]]
(let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
(if (isa? (or effective_type base_type) :type/Temporal)
(tru "{0} is {1}" item-name (humanize-datetime value unit))
(tru "{0} is {1}" item-name value)))) | |
(defmethod humanize-filter-value :>=
[root [_ field-reference value]]
(let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
(if (isa? (or effective_type base_type) :type/Temporal)
(tru "{0} is not before {1}" item-name (humanize-datetime value unit))
(tru "{0} is at least {1}" item-name value)))) | |
(defmethod humanize-filter-value :>
[root [_ field-reference value]]
(let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
(if (isa? (or effective_type base_type) :type/Temporal)
(tru "{0} is after {1}" item-name (humanize-datetime value unit))
(tru "{0} is greater than {1}" item-name value)))) | |
(defmethod humanize-filter-value :<=
[root [_ field-reference value]]
(let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
(if (isa? (or effective_type base_type) :type/Temporal)
(tru "{0} is not after {1}" item-name (humanize-datetime value unit))
(tru "{0} is no more than {1}" item-name value)))) | |
(defmethod humanize-filter-value :<
[root [_ field-reference value]]
(let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
(if (isa? (or effective_type base_type) :type/Temporal)
(tru "{0} is before {1}" item-name (humanize-datetime value unit))
(tru "{0} is less than {1}" item-name value)))) | |
(defmethod humanize-filter-value :between
[root [_ field-reference min-value max-value]]
(tru "{0} is between {1} and {2}" (item-name root field-reference) min-value max-value)) | |
(defmethod humanize-filter-value :inside
[root [_ lat-reference lon-reference lat-max lon-min lat-min lon-max]]
(tru "{0} is between {1} and {2}; and {3} is between {4} and {5}"
(item-name root lon-reference) lon-min lon-max
(item-name root lat-reference) lat-min lat-max)) | |
(defmethod humanize-filter-value :and
[root [_ & clauses]]
(->> clauses
(map (partial humanize-filter-value root))
join-enumeration)) | |
(defmethod humanize-filter-value :default
[root [_ field-reference value]]
(let [{:keys [item-name effective_type base_type unit]} (item-reference->field root field-reference)]
(if (isa? (or effective_type base_type) :type/Temporal)
(tru "{0} relates to {1}" item-name (humanize-datetime value unit))
(tru "{0} relates to {1}" item-name value)))) | |
Return a cell title given a root object and a cell query. | (defn cell-title
[root cell-query]
(str/join " " [(if-let [aggregation (get-in root [:entity :dataset_query :query :aggregation])]
(metric->description root aggregation)
(:full-name root))
(tru "where {0}" (humanize-filter-value root cell-query))])) |