From 3fb962b9b816112a33f94079b63abe862dc7fe3f Mon Sep 17 00:00:00 2001
From: Cam Saul <1455846+camsaul@users.noreply.github.com>
Date: Tue, 4 Apr 2023 10:21:05 -0700
Subject: [PATCH] Fix metric & segment description logic when definitions don't
 have `:source-table` (#29767)

---
 src/metabase/models/metric.clj        | 14 +++++++++++---
 src/metabase/models/segment.clj       | 13 ++++++++++---
 test/metabase/models/metric_test.clj  | 25 +++++++++++++++++++++++--
 test/metabase/models/segment_test.clj | 23 +++++++++++++++++++++--
 4 files changed, 65 insertions(+), 10 deletions(-)

diff --git a/src/metabase/models/metric.clj b/src/metabase/models/metric.clj
index e3b069736ec..3100253640f 100644
--- a/src/metabase/models/metric.clj
+++ b/src/metabase/models/metric.clj
@@ -17,6 +17,7 @@
    [metabase.models.serialization :as serdes]
    [metabase.util :as u]
    [metabase.util.i18n :refer [tru]]
+   [metabase.util.log :as log]
    [metabase.util.malli :as mu]
    [methodical.core :as methodical]
    [toucan.models :as models]
@@ -55,9 +56,16 @@
   [metadata-provider :- lib.metadata/MetadataProvider
    {:keys [definition], table-id :table_id, :as _metric}]
   (when (seq definition)
-    (when-let [{database-id :db_id} (lib.metadata.protocols/table metadata-provider table-id)]
-      (let [query (lib.query/query-from-legacy-inner-query metadata-provider database-id definition)]
-        (lib/describe-query query)))))
+    (when-let [{database-id :db_id} (when table-id
+                                      (lib.metadata.protocols/table metadata-provider table-id))]
+      (try
+        (let [definition (merge {:source-table table-id}
+                                definition)
+              query      (lib.query/query-from-legacy-inner-query metadata-provider database-id definition)]
+          (lib/describe-query query))
+        (catch Throwable e
+          (log/error e (tru "Error calculating Metric description: {0}" (ex-message e)))
+          nil)))))
 
 (defn- warmed-metadata-provider [metrics]
   (let [metadata-provider (doto (lib.metadata.jvm/application-database-metadata-provider)
diff --git a/src/metabase/models/segment.clj b/src/metabase/models/segment.clj
index df0e2cf6f4a..89a5b85e4db 100644
--- a/src/metabase/models/segment.clj
+++ b/src/metabase/models/segment.clj
@@ -16,6 +16,7 @@
    [metabase.models.serialization :as serdes]
    [metabase.util :as u]
    [metabase.util.i18n :refer [tru]]
+   [metabase.util.log :as log]
    [metabase.util.malli :as mu]
    [methodical.core :as methodical]
    [toucan.models :as models]
@@ -55,9 +56,15 @@
   [metadata-provider :- lib.metadata/MetadataProvider
    {table-id :table_id, :keys [definition], :as _segment}]
   (when (seq definition)
-    (when-let [{database-id :db_id} (lib.metadata.protocols/table metadata-provider table-id)]
-      (let [query (lib.query/query-from-legacy-inner-query metadata-provider database-id definition)]
-        (lib/describe-top-level-key query :filter)))))
+    (when-let [{database-id :db_id} (when table-id (lib.metadata.protocols/table metadata-provider table-id))]
+      (try
+        (let [definition (merge {:source-table table-id}
+                                definition)
+              query      (lib.query/query-from-legacy-inner-query metadata-provider database-id definition)]
+          (lib/describe-top-level-key query :filter))
+        (catch Throwable e
+          (log/error e (tru "Error calculating Segment description: {0}" (ex-message e)))
+          nil)))))
 
 (defn- warmed-metadata-provider [segments]
   (let [metadata-provider (doto (lib.metadata.jvm/application-database-metadata-provider)
diff --git a/test/metabase/models/metric_test.clj b/test/metabase/models/metric_test.clj
index e866190c62c..b4c37b26869 100644
--- a/test/metabase/models/metric_test.clj
+++ b/test/metabase/models/metric_test.clj
@@ -125,12 +125,14 @@
                (serdes/raw-hash ["measurement" (serdes/identity-hash table) now])
                (serdes/identity-hash metric)))))))
 
-(deftest definition-descriptions-test
+(deftest definition-description-missing-definition-test
   (testing ":definition_description should hydrate to nil if :definition is missing"
     (t2.with-temp/with-temp [Metric metric {:name     "Metric A"
                                             :table_id (mt/id :users)}]
       (is (= nil
-             (:definition_description (t2/hydrate metric :definition_description))))))
+             (:definition_description (t2/hydrate metric :definition_description)))))))
+
+(deftest definition-description-test
   (t2.with-temp/with-temp [Segment {segment-id :id} {:name       "Checkins with ID = 1"
                                                      :table_id   (mt/id :checkins)
                                                      :definition (:query (mt/mbql-query checkins
@@ -144,3 +146,22 @@
                                                                                [:segment segment-id]]}))}]
     (is (= "Venues, Sum of Name, Filtered by Price equals 4 and Checkins with ID = 1"
            (:definition_description (t2/hydrate metric :definition_description))))))
+
+(deftest definition-description-missing-source-table-test
+  (testing "Should work if `:definition` does not include `:source-table`"
+    (t2.with-temp/with-temp [Metric metric {:name       "Metric B"
+                                            :table_id   (mt/id :venues)
+                                            :definition (mt/$ids venues
+                                                          {:aggregation [[:sum $category_id->categories.name]]
+                                                           :filter      [:= $price 4]})}]
+      (is (= "Venues, Sum of Name, Filtered by Price equals 4"
+             (:definition_description (t2/hydrate metric :definition_description)))))))
+
+(deftest definition-description-invalid-query-test
+  (testing "Should return `nil` if query is invalid"
+    (t2.with-temp/with-temp [Metric metric {:name       "Metric B"
+                                            :table_id   (mt/id :venues)
+                                            :definition (mt/$ids venues
+                                                          {:aggregation [[:sum $category_id->categories.name]]
+                                                           :filter      [:= [:field Integer/MAX_VALUE nil] 4]})}]
+      (is (nil? (:definition_description (t2/hydrate metric :definition_description)))))))
diff --git a/test/metabase/models/segment_test.clj b/test/metabase/models/segment_test.clj
index 02f3a4e3d68..b9eeae877cb 100644
--- a/test/metabase/models/segment_test.clj
+++ b/test/metabase/models/segment_test.clj
@@ -117,12 +117,14 @@
                (serdes/raw-hash ["big customers" (serdes/identity-hash table) now])
                (serdes/identity-hash segment)))))))
 
-(deftest definition-description-test
+(deftest definition-description-missing-definition-test
   (testing "Do not hydrate definition description if definition is nil"
     (t2.with-temp/with-temp [Segment segment {:name     "Segment"
                                               :table_id (mt/id :users)}]
       (is (=? {:definition_description nil}
-              (t2/hydrate segment :definition_description)))))
+              (t2/hydrate segment :definition_description))))))
+
+(deftest definition-description-test
   (t2.with-temp/with-temp [Segment segment {:name       "Expensive BBQ Spots"
                                             :definition (:query (mt/mbql-query venues
                                                                   {:filter
@@ -140,3 +142,20 @@
                                                                           [:not-null $id]]}))}]
         (is (= "Filtered by Expensive BBQ Spots and ID is not empty"
                (:definition_description (t2/hydrate segment-2 :definition_description))))))))
+
+(deftest definition-description-missing-source-table-test
+  (testing "Should work if `:definition` does not include `:source-table`"
+    (t2.with-temp/with-temp [Segment segment {:name       "Expensive BBQ Spots"
+                                              :definition (mt/$ids venues
+                                                            {:filter
+                                                             [:= $price 4]})}]
+      (is (= "Filtered by Price equals 4"
+             (:definition_description (t2/hydrate segment :definition_description)))))))
+
+(deftest definition-description-invalid-query-test
+  (testing "Should return `nil` if query is invalid"
+    (t2.with-temp/with-temp [Segment segment {:name       "Expensive BBQ Spots"
+                                              :definition (:query (mt/mbql-query venues
+                                                                    {:filter
+                                                                     [:= [:field Integer/MAX_VALUE nil] 4]}))}]
+      (is (nil? (:definition_description (t2/hydrate segment :definition_description)))))))
-- 
GitLab