Skip to content
Snippets Groups Projects
Unverified Commit 48a30f4b authored by metamben's avatar metamben Committed by GitHub
Browse files

Migrate default temporal bucket logic from Field.ts to ML (#30745)

Fixes #30703.
parent 8ab7f26e
No related branches found
No related tags found
No related merge requests found
......@@ -196,3 +196,10 @@
"Parses a time string that has been stripped of any time zone."
[value]
(t/local-time value))
;;; ------------------------------------------------ arithmetic ------------------------------------------------------
(defn day-diff
"Returns the time elapsed between `before` and `after` in days (an integer)."
[before after]
(.toDays (t/duration before after)))
......@@ -148,3 +148,10 @@
"Parses a time string that has been stripped of any time zone."
[value]
(moment value parse-time-formats))
;;; ------------------------------------------------ arithmetic ------------------------------------------------------
(defn day-diff
"Returns the time elapsed between `before` and `after` in days."
[^moment/Moment before ^moment/Moment after]
(.diff after before "day"))
......@@ -14,6 +14,7 @@
(shared.ns/import-fn internal/same-day?)
(shared.ns/import-fn internal/same-month?)
(shared.ns/import-fn internal/same-year?)
(shared.ns/import-fn internal/day-diff)
(defn- prep-options [options]
(merge internal/default-options (u/normalize-map options)))
......
......@@ -20,6 +20,7 @@
[metabase.lib.temporal-bucket :as lib.temporal-bucket]
[metabase.lib.util :as lib.util]
[metabase.shared.util.i18n :as i18n]
[metabase.shared.util.time :as shared.ut]
[metabase.util :as u]
[metabase.util.humanization :as u.humanization]
[metabase.util.log :as log]
......@@ -286,14 +287,35 @@
[query stage-number field-ref]
(lib.temporal-bucket/available-temporal-buckets query stage-number (resolve-field-metadata query stage-number field-ref)))
(defn- fingerprint-based-default [fingerprint]
(u/ignore-exceptions
(when-let [{:keys [earliest latest]} (-> fingerprint :type :type/DateTime)]
(let [days (shared.ut/day-diff (shared.ut/coerce-to-timestamp earliest)
(shared.ut/coerce-to-timestamp latest))]
(when-not (NaN? days)
(condp > days
1 :minute
31 :day
365 :week
:month))))))
(defn- mark-default-unit [options unit]
(cond->> options
(some #(= (:unit %) unit) options)
(mapv (fn [option]
(cond-> (dissoc option :default)
(= (:unit option) unit) (assoc :default true))))))
(defmethod lib.temporal-bucket/available-temporal-buckets-method :metadata/field
[_query _stage-number field-metadata]
(let [effective-type ((some-fn :effective-type :base-type) field-metadata)]
(cond
(isa? effective-type :type/DateTime) lib.temporal-bucket/datetime-bucket-options
(isa? effective-type :type/Date) lib.temporal-bucket/date-bucket-options
(isa? effective-type :type/Time) lib.temporal-bucket/time-bucket-options
:else [])))
(let [effective-type ((some-fn :effective-type :base-type) field-metadata)
fingerprint-default (some-> field-metadata :fingerprint fingerprint-based-default)]
(cond-> (cond
(isa? effective-type :type/DateTime) lib.temporal-bucket/datetime-bucket-options
(isa? effective-type :type/Date) lib.temporal-bucket/date-bucket-options
(isa? effective-type :type/Time) lib.temporal-bucket/time-bucket-options
:else [])
fingerprint-default (mark-default-unit fingerprint-default))))
;;; ---------------------------------------- Binning ---------------------------------------------
(defmethod lib.binning/binning-method :field
......
......@@ -210,9 +210,13 @@
(deftest ^:parallel available-temporal-buckets-test
(doseq [{:keys [metadata expected-options]}
[{:metadata (get-in temporal-bucketing-mock-metadata [:fields :date])
:expected-options lib.temporal-bucket/date-bucket-options}
:expected-options (-> lib.temporal-bucket/date-bucket-options
(update 0 dissoc :default)
(assoc-in [2 :default] true))}
{:metadata (get-in temporal-bucketing-mock-metadata [:fields :datetime])
:expected-options lib.temporal-bucket/datetime-bucket-options}
:expected-options (-> lib.temporal-bucket/datetime-bucket-options
(update 2 dissoc :default)
(assoc-in [4 :default] true))}
{:metadata (get-in temporal-bucketing-mock-metadata [:fields :time])
:expected-options lib.temporal-bucket/time-bucket-options}]]
(testing (str (:base-type metadata) " Field")
......
......@@ -83,3 +83,34 @@
:display-name "Sum"
:source_alias "Orders"}]
(lib.metadata.calculation/metadata mlv2-query))))))
(deftest ^:parallel temporal-bucketing-options-test
(mt/dataset sample-dataset
(let [query {:lib/type :mbql/query
:stages [{:lib/type :mbql.stage/mbql
:fields [[:field
{:lib/uuid (str (random-uuid))}
(mt/id :products :created_at)]]
:source-table (mt/id :products)}]
:database (mt/id)}
query (lib/query (lib.metadata.jvm/application-database-metadata-provider (mt/id))
query)]
(is (= [{:unit :minute}
{:unit :hour}
{:unit :day}
{:unit :week}
{:unit :month, :default true}
{:unit :quarter}
{:unit :year}
{:unit :minute-of-hour}
{:unit :hour-of-day}
{:unit :day-of-week}
{:unit :day-of-month}
{:unit :day-of-year}
{:unit :week-of-year}
{:unit :month-of-year}
{:unit :quarter-of-year}]
(->> (lib.metadata.calculation/metadata query)
first
(lib/available-temporal-buckets query)
(mapv #(select-keys % [:unit :default]))))))))
......@@ -63,3 +63,67 @@
(is (= "Unknown unit"
(lib.temporal-bucket/describe-temporal-unit :unknown-unit)
(lib.temporal-bucket/describe-temporal-unit 2 :unknown-unit))))
(deftest ^:parallel available-temporal-buckets-test
(let [column {:description nil
:lib/type :metadata/field
:database-is-auto-increment false
:fingerprint-version 5
:base-type :type/DateTimeWithLocalTZ
:semantic-type :type/CreationTimestamp
:database-required false
:table-id 806
:name "CREATED_AT"
:coercion-strategy nil
:lib/source :source/fields
:lib/source-column-alias "CREATED_AT"
:settings nil
:caveats nil
:nfc-path nil
:database-type "TIMESTAMP WITH TIME ZONE"
:effective-type :type/DateTimeWithLocalTZ
:fk-target-field-id nil
:custom-position 0
:active true
:id 3068
:parent-id nil
:points-of-interest nil
:visibility-type :normal
:lib/desired-column-alias "CREATED_AT"
:display-name "Created At"
:position 7
:has-field-values nil
:json-unfolding false
:preview-display true
:database-position 7
:fingerprint
{:global {:distinct-count 200, :nil% 0.0}}}
expected-units #{:minute :hour
:day :week :month :quarter :year
:minute-of-hour :hour-of-day
:day-of-week :day-of-month :day-of-year
:week-of-year :month-of-year :quarter-of-year}
expected-defaults [{:lib/type :type/temporal-bucketing-option, :unit :day, :default true}]]
(testing "missing fingerprint"
(let [column (dissoc column :fingerprint)
options (lib.temporal-bucket/available-temporal-buckets-method nil -1 column)]
(is (= expected-units
(into #{} (map :unit) options)))
(is (= expected-defaults
(filter :default options)))))
(testing "existing fingerprint"
(doseq [[latest unit] {"2019-04-15T13:34:19.931Z" :month
"2017-04-15T13:34:19.931Z" :week
"2016-05-15T13:34:19.931Z" :day
"2016-04-27T13:34:19.931Z" :minute
nil :day
"garbage" :day}]
(testing latest
(let [bounds {:earliest "2016-04-26T19:29:55.147Z"
:latest latest}
column (assoc-in column [:fingerprint :type :type/DateTime] bounds)
options (lib.temporal-bucket/available-temporal-buckets-method nil -1 column)]
(is (= expected-units
(into #{} (map :unit) options)))
(is (= (assoc-in expected-defaults [0 :unit] unit)
(filter :default options)))))))))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment