diff --git a/e2e/test/scenarios/admin/datamodel/datamodel.cy.spec.js b/e2e/test/scenarios/admin/datamodel/datamodel.cy.spec.js
index 9ee4d8f8f35c823aa8ba40bdb13619d52b245293..de985c4cb099700b8ce68692174fa9ccf2725415 100644
--- a/e2e/test/scenarios/admin/datamodel/datamodel.cy.spec.js
+++ b/e2e/test/scenarios/admin/datamodel/datamodel.cy.spec.js
@@ -67,8 +67,11 @@ describe("scenarios > admin > datamodel > field > field type", () => {
     });
   }
 
-  function searchFieldType(type) {
-    cy.findByPlaceholderText("Find...").type(type);
+  function searchFieldType(value) {
+    // .type() is flaky when used for ListSearchField - typed characters can
+    // sometimes get rearranged while typing.
+    // Unclear why. Possibly because it's rendered as a virtualized list item.
+    cy.findByPlaceholderText("Find...").invoke("val", value).trigger("blur");
   }
 
   function getFKTargetField(targetField) {
@@ -100,23 +103,19 @@ describe("scenarios > admin > datamodel > field > field type", () => {
     cy.intercept("PUT", "/api/field/*").as("fieldUpdate");
   });
 
-  it(
-    "should let you change the type to 'No semantic type'",
-    { tags: "@flaky" },
-    () => {
-      visitAlias("@ORDERS_PRODUCT_ID_URL");
-      cy.wait(["@metadata", "@metadata"]);
+  it("should let you change the type to 'No semantic type'", () => {
+    visitAlias("@ORDERS_PRODUCT_ID_URL");
+    cy.wait(["@metadata", "@metadata"]);
 
-      setFieldType({ oldValue: "Foreign Key", newValue: "No semantic type" });
+    setFieldType({ oldValue: "Foreign Key", newValue: "No semantic type" });
 
-      waitAndAssertOnResponse("fieldUpdate");
+    waitAndAssertOnResponse("fieldUpdate");
 
-      cy.reload();
-      cy.wait("@metadata");
+    cy.reload();
+    cy.wait("@metadata");
 
-      getFieldType("No semantic type");
-    },
-  );
+    getFieldType("No semantic type");
+  });
 
   it("should let you change the type to 'Foreign Key' and choose the target field", () => {
     visitAlias("@ORDERS_QUANTITY_URL");
diff --git a/enterprise/backend/src/metabase_enterprise/serialization/api.clj b/enterprise/backend/src/metabase_enterprise/serialization/api.clj
index d3ec36dd3fbe888035bb2589df36ae0637f1f0c5..97e71741ea23b10875f78830d5cabd4695520f60 100644
--- a/enterprise/backend/src/metabase_enterprise/serialization/api.clj
+++ b/enterprise/backend/src/metabase_enterprise/serialization/api.clj
@@ -125,13 +125,16 @@
                                                     {:additive *additive-logging*})]
                    (try                 ; try/catch inside logging to log errors
                      (log/infof "Serdes import, size %s" size)
-                     (let [cnt  (u.compress/untgz file dst)
+                     (let [cnt  (try (u.compress/untgz file dst)
+                                     (catch Exception e
+                                       (throw (ex-info "Cannot unpack archive" {:status 422} e))))
                            path (find-serialization-dir dst)]
                        (when-not path
                          (throw (ex-info "No source dir detected. Please make sure the serialization files are in the top level dir."
-                                         {:dst   (.getPath dst)
-                                          :count cnt
-                                          :files (.listFiles dst)})))
+                                         {:status 400
+                                          :dst    (.getPath dst)
+                                          :count  cnt
+                                          :files  (.listFiles dst)})))
                        (log/infof "In total %s entries unpacked, detected source dir: %s" cnt (.getName path))
                        (serdes/with-cache
                          (-> (v2.ingest/ingest-yaml (.getPath path))
@@ -142,6 +145,7 @@
                          (log/error e "Error during serialization")
                          (log/error (serdes/strip-error e "Error during serialization"))))))]
     {:log-file      log-file
+     :status        (:status (ex-data @err))
      :error-message (when @err
                       (serdes/strip-error @err nil))
      :report        report
@@ -253,6 +257,7 @@
   (try
     (let [start              (System/nanoTime)
           {:keys [log-file
+                  status
                   error-message
                   report
                   callback]} (unpack&import (:tempfile file)
@@ -271,9 +276,13 @@
                               :error_count   (count (:errors report))
                               :success       (not error-message)
                               :error_message error-message})
-      {:status  200
-       :headers {"Content-Type" "text/plain"}
-       :body    (on-response! log-file callback)})
+      (if error-message
+        {:status  (or status 500)
+         :headers {"Content-Type" "text/plain"}
+         :body    (on-response! log-file callback)}
+        {:status  200
+         :headers {"Content-Type" "text/plain"}
+         :body    (on-response! log-file callback)}))
     (finally
       (io/delete-file (:tempfile file)))))
 
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/api_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/api_test.clj
index 7c506aa00e858f26af238d1a6b27cd819709bf8c..cc306486f8e9be22300738cf5bc45c46f1a957c9 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/api_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/api_test.clj
@@ -217,7 +217,7 @@
                                                                                   (assoc :collection_id "DoesNotExist")))))))]
                   (testing "ERROR /api/ee/serialization/import"
                     (let [res (binding [api.serialization/*additive-logging* false]
-                                (mt/user-http-request :crowberto :post 200 "ee/serialization/import"
+                                (mt/user-http-request :crowberto :post 500 "ee/serialization/import"
                                                       {:request-options {:headers {"content-type" "multipart/form-data"}}}
                                                       {:file ba}))
                           log (slurp (io/input-stream res))]
@@ -262,6 +262,13 @@
                                  "models"      "Collection,Dashboard"}
                                 (-> (snowplow-test/pop-event-data-and-user-id!) last :data))))))))
 
+              (testing "Client error /api/ee/serialization/import"
+                (let [res (mt/user-http-request :crowberto :post 422 "ee/serialization/import"
+                                                {:request-options {:headers {"content-type" "multipart/form-data"}}}
+                                                {:file (.getBytes "not an archive" "UTF-8")})
+                      log (slurp (io/input-stream res))]
+                  (is (re-find #"Cannot unpack archive" log))))
+
               (mt/with-dynamic-redefs [serdes/extract-one (extract-one-error (:entity_id card)
                                                                              (mt/dynamic-value serdes/extract-one))]
                 (testing "ERROR /api/ee/serialization/export"
diff --git a/src/metabase/query_processor/pivot/postprocess.clj b/src/metabase/query_processor/pivot/postprocess.clj
index a70414848ed9fe041381cc4b3efacefd56743a56..f57c94dc2712141aad0251c6ee55dfc4c8c62593 100644
--- a/src/metabase/query_processor/pivot/postprocess.clj
+++ b/src/metabase/query_processor/pivot/postprocess.clj
@@ -7,6 +7,7 @@
   (:require
    [clojure.math.combinatorics :as math.combo]
    [clojure.set :as set]
+   [clojure.string :as str]
    [metabase.query-processor.streaming.common :as common]
    [metabase.util.malli :as mu]
    [metabase.util.malli.registry :as mr]))
@@ -103,6 +104,13 @@
   [m k v]
   (update m k conj v))
 
+(defn- default-agg-fn
+  [agg v]
+  (when v
+    (if (number? v)
+      (+ agg v)
+      v)))
+
 (defn- update-aggregate
   "Update the given `measure-aggregations` with `new-values` using the appropriate function in the `agg-fns` map.
 
@@ -116,7 +124,7 @@
   (into {}
         (map
          (fn [[measure-key agg]]
-           (let [agg-fn (get agg-fns measure-key +) ; default aggregation is just summation
+           (let [agg-fn (get agg-fns measure-key default-agg-fn)
                  new-v  (get new-values measure-key)]
              [measure-key (if new-v
                             (agg-fn agg new-v)
@@ -133,22 +141,27 @@
         row-path           (mapv row pivot-rows)
         col-path           (mapv row pivot-cols)
         measure-vals       (select-keys row pivot-measures)
-        total-fn           (fn [m path]
+        total-fn*          (fn [m path]
                              (if (seq path)
                                (update-in m path
                                           #(update-aggregate (or % (zipmap pivot-measures (repeat 0))) measure-vals measures))
-                               m))]
+                               m))
+        total-fn           (fn [m paths]
+                             (reduce total-fn* m paths))]
     (-> pivot
         (update :row-count (fn [v] (if v (inc v) 0)))
         (update :data update-in (concat row-path col-path)
                 #(update-aggregate (or % (zipmap pivot-measures (repeat 0))) measure-vals measures))
         (update :totals (fn [totals]
                           (-> totals
-                              (total-fn [:grand-total])
-                              (total-fn row-path)
-                              (total-fn col-path)
-                              (total-fn [:section-totals (first row-path)])
-                              (total-fn (concat [:column-totals (first row-path)] col-path)))))
+                              (total-fn [[:grand-total]])
+                              (total-fn [row-path])
+                              (total-fn [col-path])
+                              (total-fn [[:section-totals (first row-path)]])
+                              #_(total-fn [(concat [:column-totals (first row-path)] col-path)])
+                              (total-fn (map (fn [part]
+                                               (concat [:column-totals] part col-path))
+                                             (rest (reductions conj [] row-path)))))))
         (update :row-values #(reduce-kv update-set % (select-keys row pivot-rows)))
         (update :column-values #(reduce-kv update-set % (select-keys row pivot-cols))))))
 
@@ -188,37 +201,43 @@
 (defn- build-row
   "Build a single row of the pivot table."
   [row-combo col-combos pivot-measures data totals row-totals? ordered-formatters row-formatters]
-  (let [row-path row-combo]
-    (concat
-     (when-not (seq row-formatters) (repeat (count pivot-measures) nil))
-     (mapv fmt row-formatters row-combo)
-     (concat
-      (for [col-combo col-combos
-            measure-key pivot-measures]
-        (fmt (get ordered-formatters measure-key)
-             (get-in data (concat row-path col-combo [measure-key]))))
-      (when row-totals?
-        (for [measure-key pivot-measures]
-          (fmt (get ordered-formatters measure-key)
-               (get-in totals (concat row-path [measure-key])))))))))
+  (let [row-path       row-combo
+        measure-values (for [col-combo   col-combos
+                             measure-key pivot-measures]
+                         (fmt (get ordered-formatters measure-key)
+                              (get-in data (concat row-path col-combo [measure-key]))))]
+    (when (some #(and (some? %) (not= "" %)) measure-values)
+      (concat
+       (when-not (seq row-formatters) (repeat (count pivot-measures) nil))
+       row-combo
+       #_(mapv fmt row-formatters row-combo)
+       (concat
+        measure-values
+        (when row-totals?
+          (for [measure-key pivot-measures]
+            (fmt (get ordered-formatters measure-key)
+                 (get-in totals (concat row-path [measure-key]))))))))))
 
 (defn- build-column-totals
   "Build column totals for a section."
-  [section col-combos pivot-measures totals row-totals? ordered-formatters pivot-rows]
-  (concat
-   (cons (format "Totals for %s" (fmt (get ordered-formatters (first pivot-rows)) section))
-         (repeat (dec (count pivot-rows)) nil))
-   (for [col-combo col-combos
-         measure-key pivot-measures]
-     (fmt (get ordered-formatters measure-key)
-          (get-in totals (concat
-                          [:column-totals section]
-                          col-combo
-                          [measure-key]))))
-   (when row-totals?
-     (for [measure-key pivot-measures]
-       (fmt (get ordered-formatters measure-key)
-            (get-in totals [:section-totals section measure-key]))))))
+  [section-path col-combos pivot-measures totals row-totals? ordered-formatters pivot-rows]
+  (let [totals-row (distinct (for [col-combo   col-combos
+                                   measure-key pivot-measures]
+                               (fmt (get ordered-formatters measure-key)
+                                    (get-in totals (concat
+                                                    [:column-totals]
+                                                    section-path
+                                                    col-combo
+                                                    [measure-key])))))]
+    (when (some #(and (some? %) (not= "" %)) totals-row)
+      (concat
+       (cons (format "Totals for %s" (fmt (get ordered-formatters (first pivot-rows)) (last section-path)))
+             (repeat (dec (count pivot-rows)) nil))
+       totals-row
+       (when row-totals?
+         (for [measure-key pivot-measures]
+           (fmt (get ordered-formatters measure-key)
+                (get-in totals (concat [:section-totals] section-path [measure-key])))))))))
 
 (defn- build-grand-totals
   "Build grand totals row."
@@ -236,29 +255,98 @@
      (fmt (get ordered-formatters measure-key)
           (get-in totals [:grand-total measure-key])))))
 
+(defn- append-totals-to-subsections
+  [pivot section col-combos ordered-formatters]
+  (let [{:keys [config
+                totals]}      pivot
+        {:keys [pivot-rows
+                pivot-measures
+                row-totals?]} config]
+    (concat
+     (reduce
+      (fn [section pivot-row-idx]
+        (mapcat
+         (fn [[k rows]]
+           (let [partial-path          (take pivot-row-idx (first rows))
+                 subtotal-path         (concat partial-path [k])
+                 total-row             (vec (build-column-totals
+                                             subtotal-path
+                                             col-combos
+                                             pivot-measures
+                                             totals
+                                             row-totals?
+                                             ordered-formatters pivot-rows))
+                 ;; inside a subsection, we know that the 'parent' subsection values will all be the same
+                 ;; so we can just grab it from the first row
+                 next-subsection-value (nth (first rows) (dec pivot-row-idx))]
+             (vec (concat
+                   rows
+                   ;; assoc the next subsection's value into the row so it stays grouped in the next reduction
+                   [(if (<= (dec pivot-row-idx) 0)
+                      total-row
+                      (assoc total-row (dec pivot-row-idx) next-subsection-value))]))))
+         (group-by (fn [r] (nth r pivot-row-idx)) section)))
+      section
+      (reverse (range 1 (dec (count pivot-rows)))))
+     [(vec (build-column-totals
+            [(ffirst section)]
+            col-combos
+            pivot-measures
+            totals
+            false #_row-totals?
+            ordered-formatters pivot-rows))])))
+
 (defn build-pivot-output
   "Arrange and format the aggregated `pivot` data."
   [pivot ordered-formatters]
-  (let [{:keys [config data totals row-values column-values]} pivot
-        {:keys [pivot-rows pivot-cols pivot-measures column-titles row-totals? col-totals?]} config
-        row-formatters (mapv #(get ordered-formatters %) pivot-rows)
-        col-formatters (mapv #(get ordered-formatters %) pivot-cols)
-        row-combos (apply math.combo/cartesian-product (map row-values pivot-rows))
-        col-combos (apply math.combo/cartesian-product (map column-values pivot-cols))
-        row-totals? (and row-totals? (boolean (seq pivot-cols)))
-        column-headers (build-column-headers config col-combos col-formatters)
-        headers (or (seq (build-headers column-headers config))
-                    [(concat
-                      (map #(get column-titles %) pivot-rows)
-                      (map #(get column-titles %) pivot-measures))])]
+  (let [{:keys [config
+                data
+                totals
+                row-values
+                column-values]} pivot
+        {:keys [pivot-rows
+                pivot-cols
+                pivot-measures
+                column-titles
+                row-totals?
+                col-totals?]}   config
+        row-formatters          (mapv #(get ordered-formatters %) pivot-rows)
+        col-formatters          (mapv #(get ordered-formatters %) pivot-cols)
+        row-combos              (apply math.combo/cartesian-product (map row-values pivot-rows))
+        col-combos              (apply math.combo/cartesian-product (map column-values pivot-cols))
+        row-totals?             (and row-totals? (boolean (seq pivot-cols)))
+        column-headers          (build-column-headers config col-combos col-formatters)
+        headers                 (or (seq (build-headers column-headers config))
+                                    [(concat
+                                      (map #(get column-titles %) pivot-rows)
+                                      (map #(get column-titles %) pivot-measures))])]
     (concat
      headers
-     (apply concat
-            (for [section-row-combos (sort-by ffirst (vals (group-by first row-combos)))]
-              (concat
-               (for [row-combo (sort-by first section-row-combos)]
-                 (build-row row-combo col-combos pivot-measures data totals row-totals? ordered-formatters row-formatters))
-               (when (and col-totals? (> (count pivot-rows) 1))
-                 [(build-column-totals (ffirst section-row-combos) col-combos pivot-measures totals row-totals? ordered-formatters pivot-rows)]))))
+     (filter seq
+             (apply concat
+                    (let [sections-rows
+                          (for [section-row-combos (sort-by ffirst (vals (group-by first row-combos)))]
+                            (concat
+                             (remove nil?
+                                     (for [row-combo (sort-by first section-row-combos)]
+                                       (build-row row-combo col-combos pivot-measures data totals row-totals? ordered-formatters row-formatters)))))]
+                      (mapv
+                       (fn [section-rows]
+                         (->>
+                          ;; section rows are either enriched with column-totals rows or left as is
+                          (if col-totals?
+                            (append-totals-to-subsections pivot section-rows col-combos ordered-formatters)
+                            section-rows)
+                          ;; then, we apply the row-formatters to the pivot-rows portion of each row,
+                          ;; filtering out any rows that begin with "Totals ..."
+                          (mapv
+                           (fn [row]
+                             (let [[row-part vals-part] (split-at (count pivot-rows) row)]
+                               (if (or
+                                    (not (seq row-part))
+                                    (str/starts-with? (first row-part) "Totals"))
+                                 row
+                                 (vec (concat (map fmt row-formatters row-part) vals-part))))))))
+                       sections-rows))))
      (when col-totals?
        [(build-grand-totals config col-combos totals row-totals? ordered-formatters)]))))
diff --git a/test/metabase/api/downloads_exports_test.clj b/test/metabase/api/downloads_exports_test.clj
index 2ab657537a7fd4f8509735ff4d791371d4ee8cb1..4ccd0dd7d412d251b1a4be90decda21b93df3f2a 100644
--- a/test/metabase/api/downloads_exports_test.clj
+++ b/test/metabase/api/downloads_exports_test.clj
@@ -279,9 +279,13 @@
         (testing "formatted"
           (is (= [[["Category" "2016" "2017" "2018" "2019" "Row totals"]
                    ["Doohickey" "$632.14" "$854.19" "$496.43" "$203.13" "$2,185.89"]
+                   ["Totals for Doohickey" "$632.14" "$854.19" "$496.43" "$203.13"]
                    ["Gadget" "$679.83" "$1,059.11" "$844.51" "$435.75" "$3,019.20"]
+                   ["Totals for Gadget" "$679.83" "$1,059.11" "$844.51" "$435.75"]
                    ["Gizmo" "$529.70" "$1,080.18" "$997.94" "$227.06" "$2,834.88"]
+                   ["Totals for Gizmo" "$529.70" "$1,080.18" "$997.94" "$227.06"]
                    ["Widget" "$987.39" "$1,014.68" "$912.20" "$195.04" "$3,109.31"]
+                   ["Totals for Widget" "$987.39" "$1,014.68" "$912.20" "$195.04"]
                    ["Grand totals" "$2,829.06" "$4,008.16" "$3,251.08" "$1,060.98" "$11,149.28"]]
                   #{:unsaved-card-download :card-download :dashcard-download
                     :alert-attachment :subscription-attachment
@@ -298,9 +302,13 @@
                     "2019-01-01T00:00:00Z"
                     "Row totals"]
                    ["Doohickey" "632.14" "854.19" "496.43" "203.13" "2185.89"]
+                   ["Totals for Doohickey" "632.14" "854.19" "496.43" "203.13"]
                    ["Gadget" "679.83" "1059.11" "844.51" "435.75" "3019.20"]
+                   ["Totals for Gadget" "679.83" "1059.11" "844.51" "435.75"]
                    ["Gizmo" "529.7" "1080.18" "997.94" "227.06" "2834.88"]
+                   ["Totals for Gizmo" "529.7" "1080.18" "997.94" "227.06"]
                    ["Widget" "987.39" "1014.68" "912.2" "195.04" "3109.31"]
+                   ["Totals for Widget" "987.39" "1014.68" "912.2" "195.04"]
                    ["Grand totals" "2829.06" "4008.16" "3251.08" "1060.98" "11149.28"]]
                   #{:unsaved-card-download :card-download :dashcard-download
                     :alert-attachment :subscription-attachment
@@ -512,12 +520,15 @@
                                                 :pivot_results true)
                           csv/read-csv)]
           (is (= [["Created At" "Category" "Sum of Price"]
-                  ["April, 2016" "Doohickey" ""]
                   ["April, 2016" "Gadget" "49.54"]
                   ["April, 2016" "Gizmo" "87.29"]
-                  ["April, 2016" "Widget" ""]
-                  ["Totals for April, 2016" "" "136.83"]]
-                 (take 6 result))))))))
+                  ["Totals for April, 2016" "" "136.83"]
+                  ["May, 2016" "Doohickey" "144.12"]
+                  ["May, 2016" "Gadget" "81.58"]
+                  ["May, 2016" "Gizmo" "75.09"]
+                  ["May, 2016" "Widget" "90.21"]
+                  ["Totals for May, 2016" "" "391"]]
+                 (take 9 result))))))))
 
 (deftest ^:parallel zero-row-pivot-tables-test
   (testing "Pivot tables with zero rows download correctly."
@@ -1154,3 +1165,43 @@
                     :dashcard-download        expected-header
                     :public-dashcard-download expected-header}
                    (update-vals formatted-results first)))))))))
+
+(deftest pivot-non-numeric-values-in-aggregations
+  (testing "A pivot table with an aggegation that results in non-numeric values (eg. Dates) will still worl (#49353)."
+    (mt/dataset test-data
+      (mt/with-temp [:model/Card card {:display                :pivot
+                                       :dataset_query          {:database (mt/id)
+                                                                :type     :query
+                                                                :query
+                                                                {:source-table (mt/id :products)
+                                                                 :aggregation  [[:count]
+                                                                                [:min
+                                                                                 [:field (mt/id :products :created_at)
+                                                                                  {:base-type :type/DateTime :temporal-unit :year}]]]
+                                                                 :breakout     [[:field (mt/id :products :category) {:base-type :type/Text}]
+                                                                                [:field (mt/id :products :created_at)
+                                                                                 {:base-type :type/DateTime :temporal-unit :year}]]}}
+                                       :visualization_settings {:pivot_table.column_split
+                                                                {:rows    [[:field (mt/id :products :created_at) {:base-type :type/DateTime :temporal-unit :year}]
+                                                                           [:field (mt/id :products :category) {:base-type :type/Text}]]
+                                                                 :columns []
+                                                                 :values  [[:aggregation 0] [:aggregation 1]]}
+                                                                :column_settings
+                                                                {"[\"name\",\"count\"]" {:column_title "Count Renamed"}}}}]
+        (let [expected-header   ["Created At" "Category" "Count" "Min of Created At: Year"]
+              formatted-results (all-downloads card {:export-format :csv :format-rows false :pivot true})]
+          (is (= {:unsaved-card-download    expected-header
+                  :card-download            expected-header
+                  :public-question-download expected-header
+                  :dashcard-download        expected-header
+                  :public-dashcard-download expected-header}
+                 (update-vals formatted-results first))))
+        (testing "The column title changes are used when format-rows is true"
+          (let [expected-header   ["Created At" "Category" "Count Renamed" "Min of Created At: Year"]
+                formatted-results (all-downloads card {:export-format :csv :format-rows true :pivot true})]
+            (is (= {:unsaved-card-download    expected-header
+                    :card-download            expected-header
+                    :public-question-download expected-header
+                    :dashcard-download        expected-header
+                    :public-dashcard-download expected-header}
+                   (update-vals formatted-results first)))))))))