From fff6e35c3ea8c68cd6032a00516337e6dfdc6427 Mon Sep 17 00:00:00 2001
From: Alexander Polyankin <alexander.polyankin@metabase.com>
Date: Tue, 20 Aug 2024 07:49:10 -0400
Subject: [PATCH] Show an error message when the temporal unit is not
 applicable to a dashboard card (#46983)

* Fix tests

* Add tests

* Add tests

* Fix types

* Fix types
---
 .../temporal-unit-parameters.cy.spec.js       | 44 +++++++++++++++++++
 frontend/src/metabase-types/api/dataset.ts    | 11 +++--
 frontend/src/metabase/dashboard/utils.ts      | 19 ++++----
 .../src/metabase/dashboard/utils.unit.spec.ts | 38 ++++++++++++++++
 4 files changed, 99 insertions(+), 13 deletions(-)

diff --git a/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js b/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js
index 17faabcf419..ae5d502b25a 100644
--- a/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js
+++ b/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js
@@ -187,6 +187,22 @@ const nativeUnitQuestionDetails = {
   },
 };
 
+const nativeTimeQuestionDetails = {
+  name: "SQL time",
+  display: "table",
+  native: {
+    query: "SELECT CAST('10:00' AS TIME) AS TIME",
+  },
+};
+
+const getNativeTimeQuestionBasedQuestionDetails = card => ({
+  query: {
+    "source-table": `card__${card.id}`,
+    aggregation: [["count"]],
+    breakout: [["field", "TIME", { "base-type": "type/Time" }]],
+  },
+});
+
 const parameterDetails = {
   id: "1",
   name: "Unit of Time",
@@ -857,6 +873,34 @@ describe("scenarios > dashboard > temporal unit parameters", () => {
       resetFilterWidgetToDefault();
       getDashboardCard().findByText("Created At: Year").should("be.visible");
     });
+
+    it("should show an error message when an incompatible temporal unit is used", () => {
+      cy.log("setup dashboard with a time column");
+      createNativeQuestion(nativeTimeQuestionDetails).then(({ body: card }) => {
+        cy.createDashboardWithQuestions({
+          questions: [getNativeTimeQuestionBasedQuestionDetails(card)],
+        }).then(({ dashboard }) => {
+          visitDashboard(dashboard.id);
+        });
+      });
+      editDashboard();
+      addTemporalUnitParameter();
+      selectDashboardFilter(getDashboardCard(), "TIME");
+      saveDashboard();
+
+      cy.log("use an invalid temporal unit");
+      filterWidget().click();
+      popover().findByText("Year").click();
+      getDashboardCard().should(
+        "contain.text",
+        "This chart can not be broken out by the selected unit of time: year.",
+      );
+
+      cy.log("use an valid temporal unit");
+      filterWidget().click();
+      popover().findByText("Minute").click();
+      getDashboardCard().findByText("TIME: Minute").should("be.visible");
+    });
   });
 
   describe("query string parameters", () => {
diff --git a/frontend/src/metabase-types/api/dataset.ts b/frontend/src/metabase-types/api/dataset.ts
index 643c14330c5..9be021ff5bf 100644
--- a/frontend/src/metabase-types/api/dataset.ts
+++ b/frontend/src/metabase-types/api/dataset.ts
@@ -74,11 +74,14 @@ export interface Dataset {
   row_count: number;
   running_time: number;
   json_query?: JsonQuery;
+  error?:
+    | string
+    | {
+        status: number; // HTTP status code
+        data?: string;
+      };
   error_type?: string;
-  error?: {
-    status: number; // HTTP status code
-    data?: string;
-  };
+  error_is_curated?: boolean;
   context?: string;
   status?: string;
 }
diff --git a/frontend/src/metabase/dashboard/utils.ts b/frontend/src/metabase/dashboard/utils.ts
index aaf7497dc13..7867dbc0c86 100644
--- a/frontend/src/metabase/dashboard/utils.ts
+++ b/frontend/src/metabase/dashboard/utils.ts
@@ -1,7 +1,6 @@
 import type { Location } from "history";
 import _ from "underscore";
 
-import { IS_EMBED_PREVIEW } from "metabase/lib/embed";
 import { SERVER_ERROR_TYPES } from "metabase/lib/errors";
 import { isJWT } from "metabase/lib/utils";
 import { isUuid } from "metabase/lib/uuid";
@@ -207,7 +206,7 @@ export function getDashcardResultsError(datasets: Dataset[]) {
   const isAccessRestricted = datasets.some(
     s =>
       s.error_type === SERVER_ERROR_TYPES.missingPermissions ||
-      s.error?.status === 403,
+      (typeof s.error === "object" && s.error?.status === 403),
   );
 
   if (isAccessRestricted) {
@@ -217,14 +216,16 @@ export function getDashcardResultsError(datasets: Dataset[]) {
     };
   }
 
-  const errors = datasets.map(s => s.error).filter(Boolean);
-  if (errors.length > 0) {
-    if (IS_EMBED_PREVIEW) {
-      const message = errors[0]?.data || getGenericErrorMessage();
-      return { message, icon: "warning" as const };
-    }
+  if (datasets.some(dataset => dataset.error)) {
+    const curatedErrorDataset = datasets.find(
+      dataset => dataset.error && dataset.error_is_curated,
+    );
+
     return {
-      message: getGenericErrorMessage(),
+      message:
+        typeof curatedErrorDataset?.error === "string"
+          ? curatedErrorDataset.error
+          : getGenericErrorMessage(),
       icon: "warning" as const,
     };
   }
diff --git a/frontend/src/metabase/dashboard/utils.unit.spec.ts b/frontend/src/metabase/dashboard/utils.unit.spec.ts
index 6ecdf1f1044..a3798b93b84 100644
--- a/frontend/src/metabase/dashboard/utils.unit.spec.ts
+++ b/frontend/src/metabase/dashboard/utils.unit.spec.ts
@@ -213,11 +213,49 @@ describe("Dashboard utils", () => {
       expect(error).toStrictEqual(expectedGenericError);
     });
 
+    it("should return a curated error in case it is set in the response", () => {
+      const error = getDashcardResultsError([
+        createMockDataset({}),
+        createMockDataset({
+          error: "Wrong query",
+          error_is_curated: true,
+        }),
+      ]);
+
+      expect(error).toEqual({
+        icon: "warning",
+        message: "Wrong query",
+      });
+    });
+
+    it("should return a generic error in case the error is curated but is not a string", () => {
+      const error = getDashcardResultsError([
+        createMockDataset({}),
+        createMockDataset({
+          error: { status: 500 },
+          error_is_curated: true,
+        }),
+      ]);
+
+      expect(error).toEqual(expectedGenericError);
+    });
+
     it("should not return any errors if there are no any errors", () => {
       const error = getDashcardResultsError([createMockDataset({})]);
 
       expect(error).toBeUndefined();
     });
+
+    it("should not return any errors if the error is curated but there is no error message or object set", () => {
+      const error = getDashcardResultsError([
+        createMockDataset({
+          error: undefined,
+          error_is_curated: true,
+        }),
+      ]);
+
+      expect(error).toBeUndefined();
+    });
   });
 
   describe("getVisibleCardIds", () => {
-- 
GitLab