Skip to content
Snippets Groups Projects
Unverified Commit 1ab3e4e9 authored by Oleksandr Yakushev's avatar Oleksandr Yakushev Committed by GitHub
Browse files

perf: Improve the performance of pivoted XLSX export (#50117)


* [xlsx-export] Cache the custom datetime formatter

* [xlsx-export] Optimize functions in m.q_p.pivot.postprocess

* cljfmt

---------

Co-authored-by: default avatarAdam James <adam.vermeer2@gmail.com>
parent 0c442b62
No related branches found
No related tags found
No related merge requests found
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
[timezone-id :- [:maybe :string] value col visualization-settings] [timezone-id :- [:maybe :string] value col visualization-settings]
(cond (cond
(types/temporal-field? col) (types/temporal-field? col)
(formatter/format-temporal-str timezone-id value col) ((formatter/make-temporal-str-formatter timezone-id col {}) value)
(number? value) (number? value)
(formatter/format-number value col visualization-settings) (formatter/format-number value col visualization-settings)
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
(p/import-vars (p/import-vars
[datetime [datetime
format-temporal-str make-temporal-str-formatter
temporal-string?]) temporal-string?])
(p.types/defrecord+ NumericWrapper [^String num-str ^Number num-value] (p.types/defrecord+ NumericWrapper [^String num-str ^Number num-value]
...@@ -275,7 +275,7 @@ ...@@ -275,7 +275,7 @@
;; for numbers, return a format function that has already computed the differences. ;; for numbers, return a format function that has already computed the differences.
;; todo: do the same for temporal strings ;; todo: do the same for temporal strings
(and apply-formatting? (types/temporal-field? col)) (and apply-formatting? (types/temporal-field? col))
#(datetime/format-temporal-str timezone-id % col visualization-settings) (datetime/make-temporal-str-formatter timezone-id col visualization-settings)
(and apply-formatting? (isa? (:semantic_type col) :type/Coordinate)) (and apply-formatting? (isa? (:semantic_type col) :type/Coordinate))
(partial format-geographic-coordinates (:semantic_type col)) (partial format-geographic-coordinates (:semantic_type col))
......
...@@ -230,14 +230,14 @@ If neither a unit nor a temporal type is provided, just bottom out by assuming a ...@@ -230,14 +230,14 @@ If neither a unit nor a temporal type is provided, just bottom out by assuming a
date-format) date-format)
temporal-str))))) temporal-str)))))
(defn format-temporal-str (defn make-temporal-str-formatter
"Reformat a temporal literal string by combining time zone, column, and viz setting information to create a final "Return a formatter which, given a temporal literal string, reformts it by combining time zone, column, and viz
desired output format." setting information to create a final desired output format."
([timezone-id temporal-str col] (format-temporal-str timezone-id temporal-str col {})) [timezone-id col viz-settings]
([timezone-id temporal-str col viz-settings] (Locale/setDefault (Locale. (public-settings/site-locale)))
(Locale/setDefault (Locale. (public-settings/site-locale))) (let [merged-viz-settings (common/normalize-keys
(let [merged-viz-settings (common/normalize-keys (common/viz-settings-for-col col viz-settings))]
(common/viz-settings-for-col col viz-settings))] (fn [temporal-str]
(if (str/blank? temporal-str) (if (str/blank? temporal-str)
"" ""
(format-timestring timezone-id temporal-str col merged-viz-settings))))) (format-timestring timezone-id temporal-str col merged-viz-settings)))))
...@@ -190,34 +190,36 @@ ...@@ -190,34 +190,36 @@
(defn- build-headers (defn- build-headers
"Combine row keys with column headers." "Combine row keys with column headers."
[column-headers {:keys [pivot-cols pivot-rows column-titles]}] [column-headers {:keys [pivot-cols pivot-rows column-titles]}]
(map (fn [h] (some->> (not-empty (filter seq column-headers))
(if (and (seq pivot-cols) (not (seq pivot-rows))) (apply mapv vector)
(concat (map #(get column-titles %) pivot-cols) h) (mapv (fn [h]
(concat (map #(get column-titles %) pivot-rows) h))) (-> (mapv #(get column-titles %)
(let [hs (filter seq column-headers)] (if (and (seq pivot-cols) (empty? pivot-rows))
(when (seq hs) pivot-cols pivot-rows))
(apply map vector hs))))) (into h))))))
(defn- build-row (defn- build-row
"Build a single row of the pivot table." "Build a single row of the pivot table."
[row-combo col-combos pivot-measures data totals row-totals? ordered-formatters row-formatters] [row-combo col-combos pivot-measures data totals row-totals? ordered-formatters row-formatters]
(let [row-path row-combo (let [row-path (vec row-combo)
measure-values (for [col-combo col-combos row-data (get-in data row-path)
measure-key pivot-measures] measure-values (vec
(fmt (get ordered-formatters measure-key) (for [measure-key pivot-measures
(get-in data (concat row-path col-combo [measure-key]))))] :let [formatter (get ordered-formatters measure-key)]
col-combo col-combos]
(fmt formatter
(as-> row-data m
(reduce get m col-combo)
(get m measure-key)))))]
(when (some #(and (some? %) (not= "" %)) measure-values) (when (some #(and (some? %) (not= "" %)) measure-values)
(concat (as-> (vec (when-not (seq row-formatters) (repeat (count pivot-measures) nil)))
(when-not (seq row-formatters) (repeat (count pivot-measures) nil)) row
row-combo (into row row-combo)
#_(mapv fmt row-formatters row-combo) (into row measure-values)
(concat (into row (when row-totals?
measure-values (for [measure-key pivot-measures]
(when row-totals? (fmt (get ordered-formatters measure-key)
(for [measure-key pivot-measures] (get-in totals (concat row-path [measure-key]))))))))))
(fmt (get ordered-formatters measure-key)
(get-in totals (concat row-path [measure-key]))))))))))
(defn- build-column-totals (defn- build-column-totals
"Build column totals for a section." "Build column totals for a section."
[section-path col-combos pivot-measures totals row-totals? ordered-formatters pivot-rows] [section-path col-combos pivot-measures totals row-totals? ordered-formatters pivot-rows]
...@@ -277,7 +279,7 @@ ...@@ -277,7 +279,7 @@
groups (group-by #(nth % idx) section)] groups (group-by #(nth % idx) section)]
(mapcat second (transform (sort groups))))) (mapcat second (transform (sort groups)))))
column-combos column-combos
(reverse (map vector (range) pivot-cols))))) (reverse (map-indexed vector pivot-cols)))))
(defn- append-totals-to-subsections (defn- append-totals-to-subsections
[pivot section col-combos ordered-formatters] [pivot section col-combos ordered-formatters]
...@@ -339,45 +341,44 @@ ...@@ -339,45 +341,44 @@
:descending reverse} direction))) :descending reverse} direction)))
row-formatters (mapv #(get ordered-formatters %) pivot-rows) row-formatters (mapv #(get ordered-formatters %) pivot-rows)
col-formatters (mapv #(get ordered-formatters %) pivot-cols) col-formatters (mapv #(get ordered-formatters %) pivot-cols)
row-combos (apply math.combo/cartesian-product (map row-values pivot-rows)) row-combos (mapv vec (apply math.combo/cartesian-product (mapv row-values pivot-rows)))
col-combos (apply math.combo/cartesian-product (map column-values pivot-cols)) col-combos (mapv vec (apply math.combo/cartesian-product (mapv column-values pivot-cols)))
col-combos (sort-column-combos config col-combos) col-combos (vec (sort-column-combos config col-combos))
row-totals? (and row-totals? (boolean (seq pivot-cols))) row-totals? (and row-totals? (boolean (seq pivot-cols)))
column-headers (build-column-headers config col-combos col-formatters) column-headers (build-column-headers config col-combos col-formatters)
headers (or (seq (build-headers column-headers config)) headers (or (not-empty (build-headers column-headers config))
[(concat [(mapv #(get column-titles %) (into (vec pivot-rows) pivot-measures))])]
(map #(get column-titles %) pivot-rows) (-> headers
(map #(get column-titles %) pivot-measures))])] (into (remove empty?)
(concat (reduce into []
headers (let [sections-rows
(filter seq (vec
(apply concat (for [section-row-combos ((get sort-fns (first pivot-rows) identity) (sort-by ffirst (vals (group-by first row-combos))))]
(let [sections-rows (into []
(for [section-row-combos ((get sort-fns (first pivot-rows) identity) (sort-by ffirst (vals (group-by first row-combos))))] (keep (fn [row-combo]
(concat (build-row row-combo col-combos pivot-measures data totals row-totals? ordered-formatters row-formatters)))
(remove nil? section-row-combos)))]
(for [row-combo section-row-combos] (mapv
(build-row row-combo col-combos pivot-measures data totals row-totals? ordered-formatters row-formatters)))))] (fn [section-rows]
(mapv (->>
(fn [section-rows] section-rows
(->> (sort-pivot-subsections config)
section-rows ;; section rows are either enriched with column-totals rows or left as is
(sort-pivot-subsections config) ((fn [rows]
;; section rows are either enriched with column-totals rows or left as is (if (and col-totals? (> (count pivot-rows) 1))
((fn [rows] (append-totals-to-subsections pivot rows col-combos ordered-formatters)
(if (and col-totals? (> (count pivot-rows) 1)) rows)))
(append-totals-to-subsections pivot rows col-combos ordered-formatters) ;; then, we apply the row-formatters to the pivot-rows portion of each row,
rows))) ;; filtering out any rows that begin with "Totals ..."
;; then, we apply the row-formatters to the pivot-rows portion of each row, (mapv
;; filtering out any rows that begin with "Totals ..." (fn [row]
(mapv (let [[row-part vals-part] (split-at (count pivot-rows) row)]
(fn [row] (if (or
(let [[row-part vals-part] (split-at (count pivot-rows) row)] (not (seq row-part))
(if (or (str/starts-with? (first row-part) "Totals"))
(not (seq row-part)) row
(str/starts-with? (first row-part) "Totals")) (into (mapv fmt row-formatters row-part) vals-part)))))))
row sections-rows))))
(vec (concat (map fmt row-formatters row-part) vals-part)))))))) (into
sections-rows)))) (when col-totals?
(when col-totals? [(build-grand-totals config col-combos totals row-totals? ordered-formatters)])))))
[(build-grand-totals config col-combos totals row-totals? ordered-formatters)]))))
...@@ -25,85 +25,90 @@ ...@@ -25,85 +25,90 @@
(testing "The default behavior when the :time-enabled key is absent is to show minutes" (testing "The default behavior when the :time-enabled key is absent is to show minutes"
(is (= "h:mm a" (#'datetime/determine-time-format {})))))) (is (= "h:mm a" (#'datetime/determine-time-format {}))))))
(defn- format-temporal-str
([timezone-id temporal-str col] (format-temporal-str timezone-id temporal-str col {}))
([timezone-id temporal-str col viz-settings]
((datetime/make-temporal-str-formatter timezone-id col viz-settings) temporal-str)))
(deftest format-temporal-str-test (deftest format-temporal-str-test
(mt/with-temporary-setting-values [custom-formatting nil] (mt/with-temporary-setting-values [custom-formatting nil]
(testing "Null values do not blow up" (testing "Null values do not blow up"
(is (= "" (is (= ""
(datetime/format-temporal-str "UTC" nil :now)))) (format-temporal-str "UTC" nil :now))))
(testing "Temporal Units are formatted" (testing "Temporal Units are formatted"
(testing :minute (testing :minute
(is (= "July 16, 2020, 6:04 PM" (is (= "July 16, 2020, 6:04 PM"
(datetime/format-temporal-str "UTC" now {:unit :minute})))) (format-temporal-str "UTC" now {:unit :minute}))))
(testing :hour (testing :hour
(is (= "July 16, 2020, 6 PM" (is (= "July 16, 2020, 6 PM"
(datetime/format-temporal-str "UTC" now {:unit :hour})))) (format-temporal-str "UTC" now {:unit :hour}))))
(testing :day (testing :day
(is (= "Thursday, July 16, 2020" (is (= "Thursday, July 16, 2020"
(datetime/format-temporal-str "UTC" now {:unit :day})))) (format-temporal-str "UTC" now {:unit :day}))))
(testing :week (testing :week
(is (= "July 16, 2020 - July 22, 2020" (is (= "July 16, 2020 - July 22, 2020"
(datetime/format-temporal-str "UTC" now {:unit :week})))) (format-temporal-str "UTC" now {:unit :week}))))
(testing :month (testing :month
(is (= "July, 2020" (is (= "July, 2020"
(datetime/format-temporal-str "UTC" now {:unit :month})))) (format-temporal-str "UTC" now {:unit :month}))))
(testing :quarter (testing :quarter
(is (= "Q3 - 2020" (is (= "Q3 - 2020"
(datetime/format-temporal-str "UTC" now {:unit :quarter})))) (format-temporal-str "UTC" now {:unit :quarter}))))
(testing :year (testing :year
(is (= "2020" (is (= "2020"
(datetime/format-temporal-str "UTC" now {:unit :year}))))) (format-temporal-str "UTC" now {:unit :year})))))
(testing "x-of-y Temporal Units are formatted" (testing "x-of-y Temporal Units are formatted"
(testing :minute-of-hour (testing :minute-of-hour
(is (= "1st" (is (= "1st"
(datetime/format-temporal-str "UTC" "1" {:unit :minute-of-hour})))) (format-temporal-str "UTC" "1" {:unit :minute-of-hour}))))
(testing :day-of-month (testing :day-of-month
(is (= "2nd" (is (= "2nd"
(datetime/format-temporal-str "UTC" "2" {:unit :day-of-month})))) (format-temporal-str "UTC" "2" {:unit :day-of-month}))))
(testing :day-of-year (testing :day-of-year
(is (= "203rd" (is (= "203rd"
(datetime/format-temporal-str "UTC" "203" {:unit :day-of-year})))) (format-temporal-str "UTC" "203" {:unit :day-of-year}))))
(testing :week-of-year (testing :week-of-year
(is (= "44th" (is (= "44th"
(datetime/format-temporal-str "UTC" "44" {:unit :week-of-year})))) (format-temporal-str "UTC" "44" {:unit :week-of-year}))))
(testing :day-of-week (testing :day-of-week
(is (= "Thursday" (is (= "Thursday"
(datetime/format-temporal-str "UTC" "4" {:unit :day-of-week})))) (format-temporal-str "UTC" "4" {:unit :day-of-week}))))
(testing :month-of-year (testing :month-of-year
(is (= "May" (is (= "May"
(datetime/format-temporal-str "UTC" "5" {:unit :month-of-year})))) (format-temporal-str "UTC" "5" {:unit :month-of-year}))))
(testing :quarter-of-year (testing :quarter-of-year
(is (= "Q3" (is (= "Q3"
(datetime/format-temporal-str "UTC" "3" {:unit :quarter-of-year})))) (format-temporal-str "UTC" "3" {:unit :quarter-of-year}))))
(testing :hour-of-day (testing :hour-of-day
(is (= "4 AM" (is (= "4 AM"
(datetime/format-temporal-str "UTC" "4" {:unit :hour-of-day}))))) (format-temporal-str "UTC" "4" {:unit :hour-of-day})))))
(testing "Can render time types (#15146)" (testing "Can render time types (#15146)"
(is (= "8:05 AM" (is (= "8:05 AM"
(datetime/format-temporal-str "UTC" "08:05:06Z" (format-temporal-str "UTC" "08:05:06Z"
{:effective_type :type/Time})))) {:effective_type :type/Time}))))
(testing "Can render date time types (Part of resolving #36484)" (testing "Can render date time types (Part of resolving #36484)"
(is (= "April 1, 2014, 8:30 AM" (is (= "April 1, 2014, 8:30 AM"
(datetime/format-temporal-str "UTC" "2014-04-01T08:30:00" (format-temporal-str "UTC" "2014-04-01T08:30:00"
{:effective_type :type/DateTime})))) {:effective_type :type/DateTime}))))
(testing "When `:time_enabled` is `nil` the time is truncated for date times." (testing "When `:time_enabled` is `nil` the time is truncated for date times."
(is (= "April 1, 2014" (is (= "April 1, 2014"
(datetime/format-temporal-str "UTC" "2014-04-01T08:30:00" (format-temporal-str "UTC" "2014-04-01T08:30:00"
{:effective_type :type/DateTime {:effective_type :type/DateTime
:settings {:time_enabled nil}})))) :settings {:time_enabled nil}}))))
(testing "When `:time_enabled` is `nil` the time is truncated for times (even though this may not make sense)." (testing "When `:time_enabled` is `nil` the time is truncated for times (even though this may not make sense)."
(is (= "" (is (= ""
(datetime/format-temporal-str "UTC" "08:05:06Z" (format-temporal-str "UTC" "08:05:06Z"
{:effective_type :type/Time {:effective_type :type/Time
:settings {:time_enabled nil}})))))) :settings {:time_enabled nil}}))))))
(deftest format-temporal-str-column-viz-settings-test (deftest format-temporal-str-column-viz-settings-test
(mt/with-temporary-setting-values [custom-formatting nil] (mt/with-temporary-setting-values [custom-formatting nil]
(testing "Written Date Formatting" (testing "Written Date Formatting"
(let [fmt (fn [col-viz] (let [fmt (fn [col-viz]
(datetime/format-temporal-str "UTC" now {:field_ref [:column_name "created_at"] (format-temporal-str "UTC" now {:field_ref [:column_name "created_at"]
:effective_type :type/Date} :effective_type :type/Date}
{::mb.viz/column-settings {::mb.viz/column-settings
{{::mb.viz/column-name "created_at"} col-viz}}))] {{::mb.viz/column-name "created_at"} col-viz}}))]
(doseq [[date-style normal-result abbreviated-result] (doseq [[date-style normal-result abbreviated-result]
[["MMMM D, YYYY" "July 16, 2020" "Jul 16, 2020"] [["MMMM D, YYYY" "July 16, 2020" "Jul 16, 2020"]
["D MMMM, YYYY" "16 July, 2020" "16 Jul, 2020"] ["D MMMM, YYYY" "16 July, 2020" "16 Jul, 2020"]
...@@ -118,10 +123,10 @@ ...@@ -118,10 +123,10 @@
(when date-style {::mb.viz/date-style date-style}))))))))) (when date-style {::mb.viz/date-style date-style})))))))))
(testing "Numerical Date Formatting" (testing "Numerical Date Formatting"
(let [fmt (fn [col-viz] (let [fmt (fn [col-viz]
(datetime/format-temporal-str "UTC" now {:field_ref [:column_name "created_at"] (format-temporal-str "UTC" now {:field_ref [:column_name "created_at"]
:effective_type :type/Date} :effective_type :type/Date}
{::mb.viz/column-settings {::mb.viz/column-settings
{{::mb.viz/column-name "created_at"} col-viz}}))] {{::mb.viz/column-name "created_at"} col-viz}}))]
(doseq [[date-style slash-result dash-result dot-result] (doseq [[date-style slash-result dash-result dot-result]
[["M/D/YYYY" "7/16/2020" "7-16-2020" "7.16.2020"] [["M/D/YYYY" "7/16/2020" "7-16-2020" "7.16.2020"]
["D/M/YYYY" "16/7/2020" "16-7-2020" "16.7.2020"] ["D/M/YYYY" "16/7/2020" "16-7-2020" "16.7.2020"]
...@@ -145,15 +150,15 @@ ...@@ -145,15 +150,15 @@
(let [global-settings (m/map-vals mb.viz/db->norm-column-settings-entries (let [global-settings (m/map-vals mb.viz/db->norm-column-settings-entries
(public-settings/custom-formatting))] (public-settings/custom-formatting))]
(is (= "Jul 16, 2020" (is (= "Jul 16, 2020"
(datetime/format-temporal-str "UTC" now (format-temporal-str "UTC" now
{:effective_type :type/Date} {:effective_type :type/Date}
{::mb.viz/global-column-settings global-settings}))))) {::mb.viz/global-column-settings global-settings})))))
(mt/with-temporary-setting-values [custom-formatting {:type/Temporal {:date_style "M/DD/YYYY" (mt/with-temporary-setting-values [custom-formatting {:type/Temporal {:date_style "M/DD/YYYY"
:date_separator "-"}}] :date_separator "-"}}]
(let [global-settings (m/map-vals mb.viz/db->norm-column-settings-entries (let [global-settings (m/map-vals mb.viz/db->norm-column-settings-entries
(public-settings/custom-formatting))] (public-settings/custom-formatting))]
(is (= "7-16-2020, 6:04 PM" (is (= "7-16-2020, 6:04 PM"
(datetime/format-temporal-str (format-temporal-str
"UTC" "UTC"
now now
{:effective_type :type/DateTime} {:effective_type :type/DateTime}
...@@ -175,21 +180,21 @@ ...@@ -175,21 +180,21 @@
time-str "2023-12-11T21:51:57.265914Z"] time-str "2023-12-11T21:51:57.265914Z"]
(testing "Global settings are applied to a :type/DateTimeDateTime" (testing "Global settings are applied to a :type/DateTimeDateTime"
(is (= "December 11, 2023, 21:51" (is (= "December 11, 2023, 21:51"
(datetime/format-temporal-str "UTC" time-str col common-viz-settings)))) (format-temporal-str "UTC" time-str col common-viz-settings))))
(testing "A :type/DateTimeDateTimeWithLocalTZ is a :type/DateTimeDateTime" (testing "A :type/DateTimeDateTimeWithLocalTZ is a :type/DateTimeDateTime"
(is (= "December 11, 2023, 21:51" (is (= "December 11, 2023, 21:51"
(let [col (assoc col :base_type :type/DateTimeWithLocalTZ)] (let [col (assoc col :base_type :type/DateTimeWithLocalTZ)]
(datetime/format-temporal-str "UTC" time-str col common-viz-settings))))) (format-temporal-str "UTC" time-str col common-viz-settings)))))
(testing "Custom settings are applied when the column has them" (testing "Custom settings are applied when the column has them"
;; Note that the time style of the column setting has precedence over the global setting ;; Note that the time style of the column setting has precedence over the global setting
(is (= "Monday, December 11, 2023, 9:51:57.265 PM" (is (= "Monday, December 11, 2023, 9:51:57.265 PM"
(let [col (assoc col :name "CUSTOM_DATETIME")] (let [col (assoc col :name "CUSTOM_DATETIME")]
(datetime/format-temporal-str "UTC" time-str col common-viz-settings))))) (format-temporal-str "UTC" time-str col common-viz-settings)))))
(testing "Column metadata settings are applied" (testing "Column metadata settings are applied"
(is (= "Dec 11, 2023, 21:51:57" (is (= "Dec 11, 2023, 21:51:57"
(let [col (assoc col :settings {:time_enabled "seconds" (let [col (assoc col :settings {:time_enabled "seconds"
:date_abbreviate true})] :date_abbreviate true})]
(datetime/format-temporal-str "UTC" time-str col common-viz-settings))))) (format-temporal-str "UTC" time-str col common-viz-settings)))))
(testing "Various settings can be merged" (testing "Various settings can be merged"
(testing "We abbreviate the base case..." (testing "We abbreviate the base case..."
(is (= "Dec 11, 2023, 21:51" (is (= "Dec 11, 2023, 21:51"
...@@ -198,7 +203,7 @@ ...@@ -198,7 +203,7 @@
:type/Temporal :type/Temporal
::mb.viz/date-abbreviate] ::mb.viz/date-abbreviate]
true)] true)]
(datetime/format-temporal-str "UTC" time-str col common-viz-settings))))) (format-temporal-str "UTC" time-str col common-viz-settings)))))
(testing "...and we abbreviate the custome column formatting as well" (testing "...and we abbreviate the custome column formatting as well"
(is (= "Mon, Dec 11, 2023, 9:51:57.265 PM" (is (= "Mon, Dec 11, 2023, 9:51:57.265 PM"
(let [col (assoc col :name "CUSTOM_DATETIME") (let [col (assoc col :name "CUSTOM_DATETIME")
...@@ -207,15 +212,15 @@ ...@@ -207,15 +212,15 @@
:type/Temporal :type/Temporal
::mb.viz/date-abbreviate] ::mb.viz/date-abbreviate]
true)] true)]
(datetime/format-temporal-str "UTC" time-str col common-viz-settings)))))) (format-temporal-str "UTC" time-str col common-viz-settings))))))
(testing "The appropriate formatting is applied when the column type is date" (testing "The appropriate formatting is applied when the column type is date"
(is (= "December 11, 2023" (is (= "December 11, 2023"
(let [col (assoc col :effective_type :type/Date)] (let [col (assoc col :effective_type :type/Date)]
(datetime/format-temporal-str "UTC" time-str col common-viz-settings))))) (format-temporal-str "UTC" time-str col common-viz-settings)))))
(testing "The appropriate formatting is applied when the column type is time" (testing "The appropriate formatting is applied when the column type is time"
(is (= "21:51" (is (= "21:51"
(let [col (assoc col :effective_type :type/Time)] (let [col (assoc col :effective_type :type/Time)]
(datetime/format-temporal-str "UTC" time-str col common-viz-settings))))) (format-temporal-str "UTC" time-str col common-viz-settings)))))
(testing "Formatting works for times with a custom time-enabled" (testing "Formatting works for times with a custom time-enabled"
(is (= "21:51:57.265" (is (= "21:51:57.265"
(let [col (assoc col :effective_type :type/Time) (let [col (assoc col :effective_type :type/Time)
...@@ -224,7 +229,7 @@ ...@@ -224,7 +229,7 @@
:type/Temporal :type/Temporal
::mb.viz/time-enabled] ::mb.viz/time-enabled]
"milliseconds")] "milliseconds")]
(datetime/format-temporal-str "UTC" time-str col common-viz-settings))))))))) (format-temporal-str "UTC" time-str col common-viz-settings)))))))))
(deftest format-default-unit-test (deftest format-default-unit-test
(testing "When the unit is :default we use the column type." (testing "When the unit is :default we use the column type."
...@@ -233,14 +238,14 @@ ...@@ -233,14 +238,14 @@
:effective_type :type/Time :effective_type :type/Time
:base_type :type/Time}] :base_type :type/Time}]
(is (= "3:30 PM" (is (= "3:30 PM"
(datetime/format-temporal-str "UTC" "15:30:45Z" col nil)))))) (format-temporal-str "UTC" "15:30:45Z" col nil))))))
(testing "Corner case: Return the time string when there is no useful information about it _and_ it's not formattable." (testing "Corner case: Return the time string when there is no useful information about it _and_ it's not formattable."
;; This addresses a rare case (might never happen IRL) in which we try to apply the default formatting of ;; This addresses a rare case (might never happen IRL) in which we try to apply the default formatting of
;; "MMMM d, yyyy" to a time, but we don't know it's a time so we error our. ;; "MMMM d, yyyy" to a time, but we don't know it's a time so we error our.
(mt/with-temporary-setting-values [custom-formatting nil] (mt/with-temporary-setting-values [custom-formatting nil]
(let [col {:unit :default}] (let [col {:unit :default}]
(is (= "15:30:45Z" (is (= "15:30:45Z"
(datetime/format-temporal-str "UTC" "15:30:45Z" col nil))))))) (format-temporal-str "UTC" "15:30:45Z" col nil)))))))
(deftest ^:parallel year-in-dates-near-start-or-end-of-year-is-correct-test (deftest ^:parallel year-in-dates-near-start-or-end-of-year-is-correct-test
(testing "When the date is at the start/end of the year, the year is formatted properly. (#40306)" (testing "When the date is at the start/end of the year, the year is formatted properly. (#40306)"
...@@ -252,9 +257,9 @@ ...@@ -252,9 +257,9 @@
;; What we probably do want is 'yyyy' which calculates what day of the year the date is and then returns the year. ;; What we probably do want is 'yyyy' which calculates what day of the year the date is and then returns the year.
(let [dates (fn [year] [(format "%s-01-01" year) (format "%s-12-31" year)]) (let [dates (fn [year] [(format "%s-01-01" year) (format "%s-12-31" year)])
fmt (fn [s] fmt (fn [s]
(datetime/format-temporal-str "UTC" s {:field_ref [:column_name "created_at"] (format-temporal-str "UTC" s {:field_ref [:column_name "created_at"]
:effective_type :type/Date} :effective_type :type/Date}
{::mb.viz/column-settings {::mb.viz/column-settings
{{::mb.viz/column-name "created_at"} {::mb.viz/date-style "YYYY-MM-dd"}}}))] {{::mb.viz/column-name "created_at"} {::mb.viz/date-style "YYYY-MM-dd"}}}))]
(doseq [the-date (mapcat dates (range 2008 3008))] (doseq [the-date (mapcat dates (range 2008 3008))]
(is (= the-date (fmt the-date))))))) (is (= the-date (fmt the-date)))))))
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