From 8aa2cc2f601c6614532c5e0cc3f3b220719226a6 Mon Sep 17 00:00:00 2001
From: Cal Herries <39073188+calherries@users.noreply.github.com>
Date: Wed, 3 May 2023 11:04:14 +0530
Subject: [PATCH] Use display name from model for implicit actions (#30356)

* Use display name from card for implicit actions

* Remove fields arg from generateFieldSettingsFromParameters

* Update implicit action test to include display-name

* Tidy

* Fix unit tests

* Fix e2e test

* Only include display-name in the parameters for implicit action types

* Use `field.displayName()`

* Fix some tests

* Attempt to fix test

* Fix ActionParametersInputForm unit tests

* Only generate field settings from parameters if action.type is implicit

* Fix e2e tests

* Fix unit tests

* Fix tests
---
 .../actions-on-dashboards.cy.spec.js          |  32 +--
 .../scenarios/models/model-actions.cy.spec.js |   4 +-
 .../src/metabase-types/api/mocks/actions.ts   |   1 +
 .../components/ActionViz/Action.unit.spec.tsx |  18 +-
 .../ActionParametersInputForm.unit.spec.tsx   |  38 +++-
 .../actions/hooks/use-action-form/utils.ts    |  41 ++--
 .../hooks/use-action-form/utils.unit.spec.ts  | 193 +++---------------
 .../PublicAction/PublicAction.unit.spec.tsx   |   2 +
 src/metabase/models/action.clj                |  10 +-
 test/metabase/models/action_test.clj          |  24 ++-
 10 files changed, 138 insertions(+), 225 deletions(-)

diff --git a/e2e/test/scenarios/dashboard/actions-on-dashboards.cy.spec.js b/e2e/test/scenarios/dashboard/actions-on-dashboards.cy.spec.js
index e1095a3b79b..f3a60bf95a9 100644
--- a/e2e/test/scenarios/dashboard/actions-on-dashboards.cy.spec.js
+++ b/e2e/test/scenarios/dashboard/actions-on-dashboards.cy.spec.js
@@ -140,7 +140,7 @@ const MODEL_NAME = "Test Action Model";
           clickHelper("Create");
 
           modal().within(() => {
-            cy.findByPlaceholderText("Team name").type("Zany Zebras");
+            cy.findByPlaceholderText("Team Name").type("Zany Zebras");
             cy.findByPlaceholderText("Score").type("44");
 
             cy.button("Save").click();
@@ -181,7 +181,7 @@ const MODEL_NAME = "Test Action Model";
           cy.wait("@executePrefetch");
           // let's check that the existing values are pre-filled correctly
           modal().within(() => {
-            cy.findByPlaceholderText("Team name")
+            cy.findByPlaceholderText("Team Name")
               .should("have.value", "Energetic Elephants")
               .clear()
               .type("Emotional Elephants");
@@ -229,7 +229,7 @@ const MODEL_NAME = "Test Action Model";
           clickHelper("Delete");
 
           modal().within(() => {
-            cy.findByPlaceholderText("Id").type("3");
+            cy.findByPlaceholderText("ID").type("3");
             cy.button("Delete").click();
           });
 
@@ -283,7 +283,7 @@ const MODEL_NAME = "Test Action Model";
 
           modal().within(() => {
             changeValue({
-              fieldName: "Uuid",
+              fieldName: "UUID",
               fieldType: "text",
               oldValue: oldRow.uuid,
               newValue: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a77",
@@ -321,7 +321,7 @@ const MODEL_NAME = "Test Action Model";
 
             // we can't assert on this value because mysql and postgres seem to
             // handle timezones differently 🥴
-            cy.findByPlaceholderText("Timestamptz")
+            cy.findByPlaceholderText("TimestampTZ")
               .should("have.attr", "type", "datetime-local")
               .clear()
               .type("2020-05-01T16:45:00");
@@ -370,12 +370,12 @@ const MODEL_NAME = "Test Action Model";
           clickHelper("Create");
 
           modal().within(() => {
-            cy.findByPlaceholderText("Uuid").type(
+            cy.findByPlaceholderText("UUID").type(
               "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a15",
             );
 
             cy.findByPlaceholderText("Integer").type("-20");
-            cy.findByPlaceholderText("Integerunsigned").type("20");
+            cy.findByPlaceholderText("IntegerUnsigned").type("20");
             cy.findByPlaceholderText("Tinyint").type("101");
             cy.findByPlaceholderText("Tinyint1").type("1");
             cy.findByPlaceholderText("Smallint").type("32767");
@@ -392,10 +392,10 @@ const MODEL_NAME = "Test Action Model";
 
             cy.findByPlaceholderText("Date").type("2020-02-01");
             cy.findByPlaceholderText("Datetime").type("2020-03-01T12:00:00");
-            cy.findByPlaceholderText("Datetimetz").type("2020-03-01T12:00:00");
+            cy.findByPlaceholderText("DatetimeTZ").type("2020-03-01T12:00:00");
             cy.findByPlaceholderText("Time").type("12:57:57");
             cy.findByPlaceholderText("Timestamp").type("2020-03-01T12:00:00");
-            cy.findByPlaceholderText("Timestamptz").type("2020-03-01T12:00:00");
+            cy.findByPlaceholderText("TimestampTZ").type("2020-03-01T12:00:00");
 
             cy.button("Save").click();
           });
@@ -456,9 +456,9 @@ const MODEL_NAME = "Test Action Model";
           clickHelper("Create");
 
           modal().within(() => {
-            cy.findByPlaceholderText("Uuid").should("be.visible");
-            cy.findByPlaceholderText("Json").should("not.exist");
-            cy.findByPlaceholderText("Jsonb").should("not.exist");
+            cy.findByPlaceholderText("UUID").should("be.visible");
+            cy.findByPlaceholderText("JSON").should("not.exist");
+            cy.findByPlaceholderText("JSONB").should("not.exist");
             cy.findByPlaceholderText("Binary").should("not.exist");
 
             if (dialect === "mysql") {
@@ -524,14 +524,14 @@ const MODEL_NAME = "Test Action Model";
             // the instance is in US/Pacific so it's -8 hours
             if (dialect === "postgres") {
               changeValue({
-                fieldName: "Datetimetz",
+                fieldName: "DatetimeTZ",
                 fieldType: "datetime-local",
                 oldValue: "2020-01-01T00:35:55",
                 newValue: newTime,
               });
 
               changeValue({
-                fieldName: "Timestamptz",
+                fieldName: "TimestampTZ",
                 fieldType: "datetime-local",
                 oldValue: "2020-01-01T00:35:55",
                 newValue: newTime,
@@ -540,14 +540,14 @@ const MODEL_NAME = "Test Action Model";
 
             if (dialect === "mysql") {
               changeValue({
-                fieldName: "Datetimetz",
+                fieldName: "DatetimeTZ",
                 fieldType: "datetime-local",
                 oldValue: oldRow.datetimeTZ.replace(" ", "T"),
                 newValue: newTime,
               });
 
               changeValue({
-                fieldName: "Timestamptz",
+                fieldName: "TimestampTZ",
                 fieldType: "datetime-local",
                 oldValue: oldRow.timestampTZ.replace(" ", "T"),
                 newValue: newTime,
diff --git a/e2e/test/scenarios/models/model-actions.cy.spec.js b/e2e/test/scenarios/models/model-actions.cy.spec.js
index 1bbebf6e312..b0fc4892c3e 100644
--- a/e2e/test/scenarios/models/model-actions.cy.spec.js
+++ b/e2e/test/scenarios/models/model-actions.cy.spec.js
@@ -48,7 +48,7 @@ const TEST_TEMPLATE_TAG = {
   id: TEST_PARAMETER.id,
   type: "number",
   name: TEST_PARAMETER.slug,
-  "display-name": "Id",
+  "display-name": TEST_PARAMETER.name,
   slug: TEST_PARAMETER.slug,
 };
 
@@ -405,7 +405,7 @@ describe(
         cy.visit(url);
 
         // team 2 has 10 points, let's give them more
-        cy.findByLabelText("Id").type("2");
+        cy.findByLabelText("ID").type("2");
         cy.findByLabelText(/score/i).type("16");
         cy.findByLabelText(/team name/i).type("Bouncy Bears");
 
diff --git a/frontend/src/metabase-types/api/mocks/actions.ts b/frontend/src/metabase-types/api/mocks/actions.ts
index f5fc4e65132..6cadc46e285 100644
--- a/frontend/src/metabase-types/api/mocks/actions.ts
+++ b/frontend/src/metabase-types/api/mocks/actions.ts
@@ -44,6 +44,7 @@ export const createMockQueryAction = ({
     archived: false,
     created_at: new Date().toISOString(),
     updated_at: new Date().toISOString(),
+    visualization_settings: { fields: {} },
     public_uuid: null,
     ...opts,
     type: "query",
diff --git a/frontend/src/metabase/actions/components/ActionViz/Action.unit.spec.tsx b/frontend/src/metabase/actions/components/ActionViz/Action.unit.spec.tsx
index be0613a20c3..c90db49a9fe 100644
--- a/frontend/src/metabase/actions/components/ActionViz/Action.unit.spec.tsx
+++ b/frontend/src/metabase/actions/components/ActionViz/Action.unit.spec.tsx
@@ -39,15 +39,29 @@ const ACTION = createMockQueryAction({
   parameters: [
     createMockActionParameter({
       id: "parameter_1",
-      type: "type/Text",
+      name: "Parameter 1",
+      type: "string/=",
       target: ["variable", ["template-tag", "1"]],
     }),
     createMockActionParameter({
       id: "parameter_2",
-      type: "type/Integer",
+      name: "Parameter 2",
+      type: "number/=",
       target: ["variable", ["template-tag", "2"]],
     }),
   ],
+  visualization_settings: {
+    fields: {
+      parameter_1: createMockFieldSettings({
+        fieldType: "string",
+        inputType: "string",
+      }),
+      parameter_2: createMockFieldSettings({
+        fieldType: "number",
+        inputType: "number",
+      }),
+    },
+  },
 });
 
 function createMockActionDashboardCard(
diff --git a/frontend/src/metabase/actions/containers/ActionParametersInputForm/ActionParametersInputForm.unit.spec.tsx b/frontend/src/metabase/actions/containers/ActionParametersInputForm/ActionParametersInputForm.unit.spec.tsx
index 1678acbea4d..62bc5f29fa6 100644
--- a/frontend/src/metabase/actions/containers/ActionParametersInputForm/ActionParametersInputForm.unit.spec.tsx
+++ b/frontend/src/metabase/actions/containers/ActionParametersInputForm/ActionParametersInputForm.unit.spec.tsx
@@ -9,6 +9,7 @@ import { render, screen } from "__support__/ui";
 import {
   createMockActionDashboardCard,
   createMockActionParameter,
+  createMockFieldSettings,
   createMockQueryAction,
   createMockImplicitQueryAction,
   createMockDashboard,
@@ -23,16 +24,30 @@ import ActionParametersInputModal, {
 
 const parameter1 = createMockActionParameter({
   id: "parameter_1",
+  name: "Parameter 1",
   type: "type/Text",
 });
 
 const parameter2 = createMockActionParameter({
   id: "parameter_2",
+  name: "Parameter 2",
   type: "type/Text",
 });
 
 const mockAction = createMockQueryAction({
   parameters: [parameter1, parameter2],
+  visualization_settings: {
+    fields: {
+      parameter_1: createMockFieldSettings({
+        id: "parameter_1",
+        placeholder: "Parameter 1 placeholder",
+      }),
+      parameter_2: createMockFieldSettings({
+        id: "parameter_1",
+        placeholder: "Parameter 2 placeholder",
+      }),
+    },
+  },
 });
 
 const defaultProps: ActionParametersInputFormProps = {
@@ -110,14 +125,16 @@ describe("Actions > ActionParametersInputForm", () => {
   });
 
   it("should generate field types from parameter types", async () => {
-    const action = createMockQueryAction({
+    const action = createMockImplicitQueryAction({
       parameters: [
         createMockActionParameter({
           id: "parameter_1",
+          "display-name": "Parameter 1",
           type: "type/Text",
         }),
         createMockActionParameter({
           id: "parameter_2",
+          "display-name": "Parameter 2",
           type: "type/Integer",
         }),
       ],
@@ -139,6 +156,18 @@ describe("Actions > ActionParametersInputForm", () => {
 
     const idParameter = createMockActionParameter({ id: "id" });
 
+    const parameter1 = createMockActionParameter({
+      id: "parameter_1",
+      type: "type/Text",
+      "display-name": "Parameter 1",
+    });
+
+    const parameter2 = createMockActionParameter({
+      id: "parameter_2",
+      type: "type/Text",
+      "display-name": "Parameter 2",
+    });
+
     await setup({
       action: createMockImplicitQueryAction({
         type: "implicit",
@@ -208,10 +237,9 @@ describe("Actions > ActionParametersInputForm", () => {
 
       expect(screen.getByText("My Test Modal")).toBeInTheDocument();
       expect(screen.getByTestId("action-form")).toBeInTheDocument();
-      expect(screen.getByPlaceholderText("Parameter 1")).toHaveAttribute(
-        "type",
-        "text",
-      );
+      expect(
+        screen.getByPlaceholderText("Parameter 1 placeholder"),
+      ).toHaveAttribute("type", "text");
     });
 
     it("should show a delete confirm message with the showConfirmMessage prop", async () => {
diff --git a/frontend/src/metabase/actions/hooks/use-action-form/utils.ts b/frontend/src/metabase/actions/hooks/use-action-form/utils.ts
index 96b8df14d47..36975a5c923 100644
--- a/frontend/src/metabase/actions/hooks/use-action-form/utils.ts
+++ b/frontend/src/metabase/actions/hooks/use-action-form/utils.ts
@@ -1,6 +1,5 @@
 import moment from "moment-timezone";
 
-import { slugify, humanize } from "metabase/lib/formatting";
 import { isEmpty } from "metabase/lib/validate";
 
 import { getDefaultFieldSettings } from "metabase/actions/utils";
@@ -116,39 +115,27 @@ export const getInputType = (param: Parameter, field?: Field) => {
   return "string";
 };
 
-export const generateFieldSettingsFromParameters = (
-  params: Parameter[],
-  fields?: Field[],
-) => {
+export const generateFieldSettingsFromParameters = (params: Parameter[]) => {
   const fieldSettings: Record<ParameterId, LocalFieldSettings> = {};
 
-  const fieldMetadataMap = Object.fromEntries(
-    fields?.map(f => [slugify(f.name), f]) ?? [],
-  );
-
   params.forEach((param, index) => {
-    const field = fieldMetadataMap[param.id]
-      ? new Field(fieldMetadataMap[param.id])
-      : new Field({
-          id: param.id,
-          name: param.id,
-          slug: param.id,
-          display_name: humanize(param.id),
-          base_type: param.type,
-          semantic_type: param.type,
-        });
-
-    const name = param["display-name"] ?? param.name ?? param.id;
-    const displayName = field?.displayName?.() ?? name;
+    const field = new Field({
+      id: param.id,
+      name: param.id,
+      slug: param.id,
+      display_name: param["display-name"],
+      base_type: param.type,
+      semantic_type: param.type,
+    });
 
     fieldSettings[param.id] = getDefaultFieldSettings({
       id: param.id,
-      name,
-      title: displayName,
-      placeholder: displayName,
-      required: !!param?.required,
+      name: param.name,
+      title: field.displayName(),
+      placeholder: field.displayName(),
+      required: !!param.required,
       order: index,
-      description: field?.description ?? "",
+      description: "",
       fieldType: getFieldType(param),
       inputType: getInputType(param, field),
     });
diff --git a/frontend/src/metabase/actions/hooks/use-action-form/utils.unit.spec.ts b/frontend/src/metabase/actions/hooks/use-action-form/utils.unit.spec.ts
index e1561455864..0196adb1b39 100644
--- a/frontend/src/metabase/actions/hooks/use-action-form/utils.unit.spec.ts
+++ b/frontend/src/metabase/actions/hooks/use-action-form/utils.unit.spec.ts
@@ -10,6 +10,7 @@ const createParameter = (options?: any) => {
   return {
     id: "test_parameter",
     name: "Test Parameter",
+    "display-name": "Test Parameter",
     type: "type/Text",
     ...options,
   };
@@ -190,10 +191,9 @@ describe("actions > containers > ActionParametersInputForm > utils", () => {
 
   describe("generateFieldSettingsFromParameters", () => {
     it("should generate settings for a string field", () => {
-      const fields = [createField({ name: "test-field" })];
-      const params = [createParameter({ id: "test-field" })];
+      const params = [createParameter({ id: "test-field", type: "type/Text" })];
       const [_id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
+        generateFieldSettingsFromParameters(params),
       );
 
       expect(settings.fieldType).toBe("string");
@@ -201,18 +201,11 @@ describe("actions > containers > ActionParametersInputForm > utils", () => {
     });
 
     it("should generate settings for an Integer field", () => {
-      const fields = [
-        createField({
-          name: "test-field",
-          base_type: "type/Integer",
-          semantic_type: "type/Integer",
-        }),
-      ];
       const params = [
         createParameter({ id: "test-field", type: "type/Integer" }),
       ];
       const [_id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
+        generateFieldSettingsFromParameters(params),
       );
 
       expect(settings.fieldType).toBe("number");
@@ -220,50 +213,35 @@ describe("actions > containers > ActionParametersInputForm > utils", () => {
     });
 
     it("should generate settings for a float field", () => {
-      const fields = [
-        createField({
-          name: "test-field",
-          base_type: "type/Float",
-          semantic_type: "type/Float",
-        }),
-      ];
       const params = [
         createParameter({ id: "test-field", type: "type/Float" }),
       ];
       const [_id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
+        generateFieldSettingsFromParameters(params),
       );
 
       expect(settings.fieldType).toBe("number");
       expect(settings.inputType).toBe("number");
     });
 
-    it("should generate settings for a category field", () => {
-      const fields = [
-        createField({ name: "test-field", semantic_type: "type/Category" }),
+    it("generates field settings for an integer field", () => {
+      const params = [
+        createParameter({ id: "test-field", type: "type/Integer" }),
       ];
-      const params = [createParameter({ id: "test-field", type: "type/Text" })];
       const [_id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
+        generateFieldSettingsFromParameters(params),
       );
 
-      expect(settings.fieldType).toBe("string");
-      expect(settings.inputType).toBe("string");
+      expect(settings.fieldType).toBe("number");
+      expect(settings.inputType).toBe("number");
     });
 
     it("should generate settings for a dateTime field", () => {
-      const fields = [
-        createField({
-          name: "test-field",
-          base_type: "type/DateTime",
-          semantic_type: "type/DateTime",
-        }),
-      ];
       const params = [
         createParameter({ id: "test-field", type: "type/DateTime" }),
       ];
       const [_id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
+        generateFieldSettingsFromParameters(params),
       );
 
       expect(settings.fieldType).toBe("string");
@@ -271,171 +249,64 @@ describe("actions > containers > ActionParametersInputForm > utils", () => {
     });
 
     it("should generate settings for a date field", () => {
-      const fields = [
-        createField({
-          name: "test-field",
-          base_type: "type/Date",
-          semantic_type: "type/Date",
-        }),
-      ];
       const params = [createParameter({ id: "test-field", type: "type/Date" })];
       const [_id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
+        generateFieldSettingsFromParameters(params),
       );
 
       expect(settings.fieldType).toBe("string");
       expect(settings.inputType).toBe("date");
     });
 
-    it("should set the parameter id as the object key", () => {
-      const fields = [createField({ name: "test-field" })];
-      const params = [createParameter({ id: "test-field" })];
-      const [id, _settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
-      );
-
-      expect(id).toEqual("test-field");
-    });
-
-    it("should get display name from field metadata", () => {
-      const fields = [createField({ name: "test-field" })];
-      const params = [createParameter({ id: "test-field" })];
+    it("generates field settings for a json field", () => {
+      const params = [
+        createParameter({ id: "test-field", type: "type/Structured" }),
+      ];
       const [_id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
+        generateFieldSettingsFromParameters(params),
       );
 
-      expect(settings.placeholder).toBe("Test Field");
-      expect(settings.title).toBe("Test Field");
+      expect(settings.fieldType).toBe("string");
+      expect(settings.inputType).toBe("text");
     });
 
-    it("matches field names to parameter ids case-insensitively", () => {
-      const fields = [createField({ name: "TEST-field" })];
+    it("should set the parameter id as the object key", () => {
       const params = [createParameter({ id: "test-field" })];
-      const [id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
+      const [id, _settings] = getFirstEntry(
+        generateFieldSettingsFromParameters(params),
       );
 
       expect(id).toEqual("test-field");
-      expect(settings.placeholder).toBe("Test Field");
-      expect(settings.title).toBe("Test Field");
-      expect(settings.name).toBe("Test Parameter");
     });
 
-    it("sets settings from parameter if there is no corresponding field", () => {
-      const fields = [createField({ name: "xyz", description: "foo bar baz" })];
-      const params = [createParameter({ id: "test-field", name: null })];
+    it("should get title and placeholder from the parameter", () => {
+      const params = [
+        createParameter({ id: "test-field", "display-name": "Test Field" }),
+      ];
       const [_id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
+        generateFieldSettingsFromParameters(params),
       );
 
-      expect(settings.placeholder).toBe("Test-field");
-      expect(settings.title).toBe("Test-field");
-      expect(settings.name).toBe("test-field");
+      expect(settings.placeholder).toBe("Test Field");
+      expect(settings.title).toBe("Test Field");
     });
 
     it("sets required prop to true", () => {
-      const fields = [createField({ name: "test-field" })];
       const params = [createParameter({ id: "test-field", required: true })];
       const [_id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
+        generateFieldSettingsFromParameters(params),
       );
 
       expect(settings.required).toBe(true);
     });
 
     it("sets required prop to false", () => {
-      const fields = [createField({ name: "test-field" })];
       const params = [createParameter({ id: "test-field", required: false })];
       const [_id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
+        generateFieldSettingsFromParameters(params),
       );
 
       expect(settings.required).toBe(false);
     });
-
-    it("sets description text", () => {
-      const fields = [
-        createField({ name: "test-field", description: "foo bar baz" }),
-      ];
-      const params = [createParameter({ id: "test-field" })];
-      const [_id, settings] = getFirstEntry(
-        generateFieldSettingsFromParameters(params, fields),
-      );
-
-      expect(settings.description).toBe("foo bar baz");
-    });
-
-    describe("without field metadata", () => {
-      it("humanizes parameter id in the field title", () => {
-        const params = [
-          createParameter({ id: "test_field", type: "type/Integer" }),
-        ];
-        const [_id, settings] = getFirstEntry(
-          generateFieldSettingsFromParameters(params),
-        );
-
-        expect(settings.title).toBe("Test field");
-      });
-
-      it("generates field settings for a numeric field", () => {
-        const params = [
-          createParameter({ id: "test-field", type: "type/Integer" }),
-        ];
-        const [_id, settings] = getFirstEntry(
-          generateFieldSettingsFromParameters(params),
-        );
-
-        expect(settings.fieldType).toBe("number");
-        expect(settings.inputType).toBe("number");
-      });
-
-      it("generates field settings for a string field", () => {
-        const params = [
-          createParameter({ id: "test-field", type: "type/String" }),
-        ];
-        const [_id, settings] = getFirstEntry(
-          generateFieldSettingsFromParameters(params),
-        );
-
-        expect(settings.fieldType).toBe("string");
-        expect(settings.inputType).toBe("string");
-      });
-
-      it("generates field settings for a date field", () => {
-        const params = [
-          createParameter({ id: "test-field", type: "type/Date" }),
-        ];
-        const [_id, settings] = getFirstEntry(
-          generateFieldSettingsFromParameters(params),
-        );
-
-        expect(settings.fieldType).toBe("string");
-        expect(settings.inputType).toBe("date");
-      });
-
-      it("generates field settings for a datetime field", () => {
-        const params = [
-          createParameter({ id: "test-field", type: "type/DateTime" }),
-        ];
-        const [_id, settings] = getFirstEntry(
-          generateFieldSettingsFromParameters(params),
-        );
-
-        expect(settings.fieldType).toBe("string");
-        expect(settings.inputType).toBe("datetime");
-      });
-
-      it("generates field settings for a json field", () => {
-        const params = [
-          createParameter({ id: "test-field", type: "type/Structured" }),
-        ];
-        const [_id, settings] = getFirstEntry(
-          generateFieldSettingsFromParameters(params),
-        );
-
-        expect(settings.fieldType).toBe("string");
-        expect(settings.inputType).toBe("text");
-      });
-    });
   });
 });
diff --git a/frontend/src/metabase/public/containers/PublicAction/PublicAction.unit.spec.tsx b/frontend/src/metabase/public/containers/PublicAction/PublicAction.unit.spec.tsx
index 11806cd875a..f327e7316f3 100644
--- a/frontend/src/metabase/public/containers/PublicAction/PublicAction.unit.spec.tsx
+++ b/frontend/src/metabase/public/containers/PublicAction/PublicAction.unit.spec.tsx
@@ -27,6 +27,7 @@ const TEST_PUBLIC_ID = "test-public-id";
 const SIZE_PARAMETER = createMockActionParameter({
   id: "size",
   name: "Size",
+  "display-name": "Size",
   type: "number/=",
   slug: "size",
   target: ["variable", ["template-tag", "size"]],
@@ -35,6 +36,7 @@ const SIZE_PARAMETER = createMockActionParameter({
 const COLOR_PARAMETER = createMockActionParameter({
   id: "color",
   name: "Color",
+  "display-name": "Color",
   type: "string/=",
   slug: "color",
   target: ["variable", ["template-tag", "color"]],
diff --git a/src/metabase/models/action.clj b/src/metabase/models/action.clj
index 9de56873748..327ebda54ce 100644
--- a/src/metabase/models/action.clj
+++ b/src/metabase/models/action.clj
@@ -206,10 +206,13 @@
                 :let [fields (:fields table)]
                 ;; Skip tables for have conflicting slugified columns i.e. table has "name" and "NAME" columns.
                 :when (unique-field-slugs? fields)
-                :let [card (get card-by-table-id (:id table))
-                      exposed-fields (into #{} (keep :id) (:result_metadata card))
+                :let [card         (get card-by-table-id (:id table))
+                      id->metadata (m/index-by :id (:result_metadata card))
                       parameters (->> fields
-                                      (filter #(contains? exposed-fields (:id %)))
+                                      ;; get display_name from metadata
+                                      (keep (fn [field]
+                                              (when-let [metadata (id->metadata (:id field))]
+                                                (assoc field :display_name (:display_name metadata)))))
                                       ;; remove exploded json fields and any structured field
                                       (remove (some-fn
                                                ;; exploded json fields can't be recombined in sql yet
@@ -220,6 +223,7 @@
                                                (comp #{:type/*} :effective_type)))
                                       (map (fn [field]
                                              {:id (u/slugify (:name field))
+                                              :display-name (:display_name field)
                                               :target [:variable [:template-tag (u/slugify (:name field))]]
                                               :type (:base_type field)
                                               :required (:database_required field)
diff --git a/test/metabase/models/action_test.clj b/test/metabase/models/action_test.clj
index f379bd632ad..3efdc3da633 100644
--- a/test/metabase/models/action_test.clj
+++ b/test/metabase/models/action_test.clj
@@ -6,6 +6,7 @@
    [metabase.driver :as driver]
    [metabase.models :refer [Action Card Dashboard DashboardCard]]
    [metabase.models.action :as action]
+   [metabase.query-processor :as qp]
    [metabase.sync :as sync]
    [metabase.test :as mt]
    [metabase.test.data.one-off-dbs :as one-off-dbs]
@@ -26,15 +27,20 @@
 (deftest hydrate-implicit-action-test
   (mt/test-drivers (mt/normal-drivers-with-feature :actions/custom)
     (mt/with-actions-test-data-and-actions-enabled
-      (mt/with-actions [{:keys [action-id] :as _context} {:type :implicit}]
-        (is (partial= {:id          action-id
-                       :name        "Update Example"
-                       :database_id (mt/id)
-                       :parameters  [(if (= driver/*driver* :h2)
-                                       {:type :type/BigInteger}
-                                       {:type :type/Integer})
-                                     {:type :type/Text, :id "name"}]}
-                      (action/select-action :id action-id))))))
+      (let [query (mt/mbql-query categories)]
+        (mt/with-actions [_model {:dataset         true
+                                  :dataset_query   query
+                                  :result_metadata (assoc-in (get-in (qp/process-query query) [:data :results_metadata :columns])
+                                                             [1 :display_name] "Display Name")}
+                          {:keys [action-id] :as _context} {:type :implicit}]
+          (is (partial= {:id          action-id
+                         :name        "Update Example"
+                         :database_id (mt/id)
+                         :parameters  [(if (= driver/*driver* :h2)
+                                         {:type :type/BigInteger}
+                                         {:type :type/Integer})
+                                       {:type :type/Text, :id "name" :display-name "Display Name"}]}
+                        (action/select-action :id action-id)))))))
   (testing "Implicit actions do not map parameters to json fields (parents or nested)"
     (mt/test-drivers (mt/normal-drivers-with-feature :actions/custom :nested-field-columns)
       (mt/dataset json
-- 
GitLab