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