From 4824543c704a3ce04e60a4151b4c6508264a3792 Mon Sep 17 00:00:00 2001
From: Ryan Laurie <30528226+iethree@users.noreply.github.com>
Date: Mon, 12 Sep 2022 13:16:31 -0600
Subject: [PATCH] Action Creator 5: Use Action Form Settings In Dashboard Modal
 Forms (#25344)

* derive parameter types from form settings on save

* update template tag types with parameter types

* improve tests

* pass action visualization settings to actionParametersInputForm
---
 .../api/writeback-form-settings.ts            |  1 +
 frontend/src/metabase-types/api/writeback.ts  |  3 +-
 .../metabase/components/form/FormWidget.jsx   |  2 +-
 .../containers/ActionParametersInputModal.tsx |  6 +-
 .../ActionCreator/FormCreator/FormCreator.tsx |  2 +-
 .../ActionCreator/FormCreator/FormField.tsx   | 44 +++++---------
 .../ActionCreator/FormCreator/constants.ts    |  8 +--
 .../ActionCreator/FormCreator/index.ts        |  1 +
 .../ActionCreator/FormCreator/utils.ts        | 59 ++++++++++++++++++-
 .../containers/ActionParametersInputForm.tsx  | 42 ++++++-------
 10 files changed, 108 insertions(+), 60 deletions(-)

diff --git a/frontend/src/metabase-types/api/writeback-form-settings.ts b/frontend/src/metabase-types/api/writeback-form-settings.ts
index 6b0df92174a..c929d4eff9f 100644
--- a/frontend/src/metabase-types/api/writeback-form-settings.ts
+++ b/frontend/src/metabase-types/api/writeback-form-settings.ts
@@ -39,6 +39,7 @@ export interface ActionFormSettings {
   fields: {
     [tagId: string]: FieldSettings;
   };
+  submitButtonLabel?: string;
   confirmMessage?: string;
   successMessage?: string;
   errorMessage?: string;
diff --git a/frontend/src/metabase-types/api/writeback.ts b/frontend/src/metabase-types/api/writeback.ts
index b91f488e46f..85e31ff9697 100644
--- a/frontend/src/metabase-types/api/writeback.ts
+++ b/frontend/src/metabase-types/api/writeback.ts
@@ -1,4 +1,4 @@
-import { Card } from "metabase-types/api";
+import { Card, ActionFormSettings } from "metabase-types/api";
 import {
   Parameter,
   ParameterId,
@@ -16,6 +16,7 @@ export interface WritebackActionBase {
   name: string;
   description: string | null;
   parameters: WritebackParameter[];
+  visualization_settings?: ActionFormSettings;
   "updated-at": string;
   "created-at": string;
 }
diff --git a/frontend/src/metabase/components/form/FormWidget.jsx b/frontend/src/metabase/components/form/FormWidget.jsx
index 9ce32a2f4b3..033c2490e48 100644
--- a/frontend/src/metabase/components/form/FormWidget.jsx
+++ b/frontend/src/metabase/components/form/FormWidget.jsx
@@ -41,7 +41,7 @@ const WIDGETS = {
   textFile: FormTextFileWidget,
 };
 
-function getWidgetComponent(formField) {
+export function getWidgetComponent(formField) {
   if (typeof formField.type === "string") {
     const widget =
       WIDGETS[formField.type] || PLUGIN_FORM_WIDGETS[formField.type];
diff --git a/frontend/src/metabase/dashboard/containers/ActionParametersInputModal.tsx b/frontend/src/metabase/dashboard/containers/ActionParametersInputModal.tsx
index ba074250d07..0192c872bb4 100644
--- a/frontend/src/metabase/dashboard/containers/ActionParametersInputModal.tsx
+++ b/frontend/src/metabase/dashboard/containers/ActionParametersInputModal.tsx
@@ -11,6 +11,7 @@ import type { State } from "metabase-types/store";
 
 import { closeActionParametersModal } from "../actions";
 import { getActionParametersModalFormProps } from "../selectors";
+import { getFormTitle } from "metabase/writeback/components/ActionCreator/FormCreator";
 
 interface OwnProps {
   action: WritebackAction;
@@ -41,11 +42,14 @@ function ActionParametersInputModal({
   action,
   closeActionParametersModal,
 }: Props) {
+  const title = getFormTitle(action);
+
   return (
     <Modal onClose={closeActionParametersModal}>
-      <ModalContent title={action.name} onClose={closeActionParametersModal}>
+      <ModalContent title={title} onClose={closeActionParametersModal}>
         <ActionParametersInputForm
           {...formProps}
+          action={action}
           onSubmitSuccess={closeActionParametersModal}
         />
       </ModalContent>
diff --git a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.tsx b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.tsx
index f2d1df1e171..d43b0c355f9 100644
--- a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.tsx
+++ b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormCreator.tsx
@@ -79,7 +79,7 @@ function FormItem({
     <FormItemWrapper>
       <FormItemName>{name}</FormItemName>
       <FormSettings>
-        <FormField type={fieldSettings.inputType} />
+        <FormField tag={tag} fieldSettings={fieldSettings} />
         <FieldSettingsPopover
           fieldSettings={fieldSettings}
           onChange={onChange}
diff --git a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormField.tsx b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormField.tsx
index 2e894c1278c..f193184b5d9 100644
--- a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormField.tsx
+++ b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/FormField.tsx
@@ -1,36 +1,20 @@
 import React from "react";
-import { t } from "ttag";
-import type { InputType } from "metabase-types/api";
+import type { FieldSettings } from "metabase-types/api";
+import type { TemplateTag } from "metabase-types/types/Query";
 
-import Input from "metabase/core/components/Input";
-import Select from "metabase/core/components/Select";
-import FormTextArea from "metabase/admin/datamodel/components/FormTextArea";
-import NumericInput from "metabase/core/components/NumericInput";
-import DateInput from "metabase/core/components/DateInput";
-import Radio from "metabase/core/components/Radio";
+import { getWidgetComponent } from "metabase/components/form/FormWidget";
 
-const getSampleOptions = () => [
-  { name: t`Option One`, value: 1 },
-  { name: t`Option Two`, value: 2 },
-  { name: t`Option Three`, value: 3 },
-];
+import { getFormFieldForParameter } from "./utils";
 
 // sample form fields
-export function FormField({ type }: { type: InputType }) {
-  switch (type) {
-    case "string":
-      return <Input />;
-    case "text":
-      return <FormTextArea />;
-    case "number":
-      return <NumericInput />;
-    case "date":
-      return <DateInput />;
-    case "dropdown":
-      return <Select options={getSampleOptions()} />;
-    case "inline-select":
-      return <Radio options={getSampleOptions()} variant="bubble" />;
-    default:
-      return <div>{t`no input selected`}</div>;
-  }
+export function FormField({
+  tag,
+  fieldSettings,
+}: {
+  tag: TemplateTag;
+  fieldSettings: FieldSettings;
+}) {
+  const fieldProps = getFormFieldForParameter(tag, fieldSettings);
+  const InputField = getWidgetComponent(fieldProps);
+  return <InputField field={fieldProps} {...fieldProps} />;
 }
diff --git a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/constants.ts b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/constants.ts
index 4930527502a..1d064dfabad 100644
--- a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/constants.ts
+++ b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/constants.ts
@@ -38,14 +38,14 @@ interface InputOptionsMap {
 }
 
 const getTextInputs = (): InputOptionType[] => [
-  {
-    value: "text",
-    name: t`long text`,
-  },
   {
     value: "string",
     name: t`text`,
   },
+  {
+    value: "text",
+    name: t`long text`,
+  },
 ];
 
 const getSelectInputs = (): InputOptionType[] => [
diff --git a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/index.ts b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/index.ts
index b1bfb030d49..821b681b85b 100644
--- a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/index.ts
+++ b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/index.ts
@@ -1 +1,2 @@
 export * from "./FormCreator";
+export * from "./utils";
diff --git a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/utils.ts b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/utils.ts
index e34d488fb0d..a7168563550 100644
--- a/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/utils.ts
+++ b/frontend/src/metabase/writeback/components/ActionCreator/FormCreator/utils.ts
@@ -1,4 +1,12 @@
-import type { ActionFormSettings, FieldSettings } from "metabase-types/api";
+import { t } from "ttag";
+
+import type {
+  ActionFormSettings,
+  WritebackAction,
+  FieldSettings,
+} from "metabase-types/api";
+import type { Parameter } from "metabase-types/types/Parameter";
+import type { TemplateTag } from "metabase-types/types/Query";
 
 export const getDefaultFormSettings = (
   overrides: Partial<ActionFormSettings> = {},
@@ -25,3 +33,52 @@ export const getDefaultFieldSettings = (
   width: "medium",
   ...overrides,
 });
+
+const getSampleOptions = () => [
+  { name: t`Option One`, value: 1 },
+  { name: t`Option Two`, value: 2 },
+  { name: t`Option Three`, value: 3 },
+];
+
+const getParameterFieldProps = (fieldSettings: FieldSettings) => {
+  switch (fieldSettings.inputType) {
+    case "string":
+      return { type: "input" };
+    case "text":
+      return { type: "text" };
+    case "number":
+      return { type: "integer" };
+    case "date":
+    case "datetime":
+    case "monthyear":
+    case "quarteryear":
+      return { type: "date", values: {} };
+    case "dropdown":
+      return {
+        type: "select",
+        options: fieldSettings.valueOptions ?? getSampleOptions(),
+      };
+    case "inline-select":
+      return {
+        type: "radio",
+        options: fieldSettings.valueOptions ?? getSampleOptions(),
+      };
+    default:
+      return { type: "input" };
+  }
+};
+
+export const getFormFieldForParameter = (
+  parameter: Parameter | TemplateTag,
+  fieldSettings: FieldSettings,
+) => ({
+  name: parameter.id,
+  title: parameter.name,
+  ...getParameterFieldProps(fieldSettings),
+});
+
+export const getFormTitle = (action: WritebackAction): string =>
+  action.visualization_settings?.name || action.name || "Action form";
+
+export const getSubmitButtonLabel = (action: WritebackAction): string =>
+  action.visualization_settings?.submitButtonLabel || t`Save`;
diff --git a/frontend/src/metabase/writeback/containers/ActionParametersInputForm.tsx b/frontend/src/metabase/writeback/containers/ActionParametersInputForm.tsx
index f1c4fc8a96d..028db561121 100644
--- a/frontend/src/metabase/writeback/containers/ActionParametersInputForm.tsx
+++ b/frontend/src/metabase/writeback/containers/ActionParametersInputForm.tsx
@@ -3,16 +3,22 @@ import { connect } from "react-redux";
 import { t } from "ttag";
 
 import Form from "metabase/containers/Form";
+import {
+  getFormFieldForParameter,
+  getSubmitButtonLabel,
+} from "metabase/writeback/components/ActionCreator/FormCreator";
 
 import type {
   ArbitraryParameterForActionExecution,
   WritebackParameter,
+  WritebackAction,
 } from "metabase-types/api";
 import type { Parameter, ParameterId } from "metabase-types/types/Parameter";
 import type { Dispatch, ReduxAction } from "metabase-types/store";
 
 interface Props {
   missingParameters: WritebackParameter[];
+  action: WritebackAction;
   onSubmit: (parameters: ArbitraryParameterForActionExecution[]) => ReduxAction;
   onSubmitSuccess: () => void;
   dispatch: Dispatch;
@@ -26,24 +32,6 @@ function getActionParameterType(parameter: Parameter) {
   return type;
 }
 
-function getParameterFieldProps(parameter: Parameter) {
-  if (parameter.type === "date/single") {
-    return { type: "date" };
-  }
-  if (parameter.type === "number/=") {
-    return { type: "integer" };
-  }
-  return { type: "input" };
-}
-
-function getFormFieldForParameter(parameter: Parameter) {
-  return {
-    name: parameter.id,
-    title: parameter.name,
-    ...getParameterFieldProps(parameter),
-  };
-}
-
 function formatParametersBeforeSubmit(
   values: Record<ParameterId, string | number>,
   missingParameters: WritebackParameter[],
@@ -68,15 +56,23 @@ function formatParametersBeforeSubmit(
 
 function ActionParametersInputForm({
   missingParameters,
+  action,
   dispatch,
   onSubmit,
   onSubmitSuccess,
 }: Props) {
+  const fieldSettings = useMemo(
+    () => action.visualization_settings?.fields ?? {},
+    [action],
+  );
+
   const form = useMemo(() => {
     return {
-      fields: missingParameters.map(getFormFieldForParameter),
+      fields: missingParameters.map(param =>
+        getFormFieldForParameter(param, fieldSettings[param.id]),
+      ),
     };
-  }, [missingParameters]);
+  }, [missingParameters, fieldSettings]);
 
   const handleSubmit = useCallback(
     params => {
@@ -90,7 +86,11 @@ function ActionParametersInputForm({
     [missingParameters, onSubmit, onSubmitSuccess, dispatch],
   );
 
-  return <Form form={form} onSubmit={handleSubmit} submitTitle={t`Execute`} />;
+  const submitButtonLabel = getSubmitButtonLabel(action);
+
+  return (
+    <Form form={form} onSubmit={handleSubmit} submitTitle={submitButtonLabel} />
+  );
 }
 
 export default connect()(ActionParametersInputForm);
-- 
GitLab