Skip to content
Snippets Groups Projects
Unverified Commit 4824543c authored by Ryan Laurie's avatar Ryan Laurie Committed by GitHub
Browse files

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
parent 5e3da93e
No related branches found
No related tags found
No related merge requests found
Showing
with 108 additions and 60 deletions
...@@ -39,6 +39,7 @@ export interface ActionFormSettings { ...@@ -39,6 +39,7 @@ export interface ActionFormSettings {
fields: { fields: {
[tagId: string]: FieldSettings; [tagId: string]: FieldSettings;
}; };
submitButtonLabel?: string;
confirmMessage?: string; confirmMessage?: string;
successMessage?: string; successMessage?: string;
errorMessage?: string; errorMessage?: string;
......
import { Card } from "metabase-types/api"; import { Card, ActionFormSettings } from "metabase-types/api";
import { import {
Parameter, Parameter,
ParameterId, ParameterId,
...@@ -16,6 +16,7 @@ export interface WritebackActionBase { ...@@ -16,6 +16,7 @@ export interface WritebackActionBase {
name: string; name: string;
description: string | null; description: string | null;
parameters: WritebackParameter[]; parameters: WritebackParameter[];
visualization_settings?: ActionFormSettings;
"updated-at": string; "updated-at": string;
"created-at": string; "created-at": string;
} }
......
...@@ -41,7 +41,7 @@ const WIDGETS = { ...@@ -41,7 +41,7 @@ const WIDGETS = {
textFile: FormTextFileWidget, textFile: FormTextFileWidget,
}; };
function getWidgetComponent(formField) { export function getWidgetComponent(formField) {
if (typeof formField.type === "string") { if (typeof formField.type === "string") {
const widget = const widget =
WIDGETS[formField.type] || PLUGIN_FORM_WIDGETS[formField.type]; WIDGETS[formField.type] || PLUGIN_FORM_WIDGETS[formField.type];
......
...@@ -11,6 +11,7 @@ import type { State } from "metabase-types/store"; ...@@ -11,6 +11,7 @@ import type { State } from "metabase-types/store";
import { closeActionParametersModal } from "../actions"; import { closeActionParametersModal } from "../actions";
import { getActionParametersModalFormProps } from "../selectors"; import { getActionParametersModalFormProps } from "../selectors";
import { getFormTitle } from "metabase/writeback/components/ActionCreator/FormCreator";
interface OwnProps { interface OwnProps {
action: WritebackAction; action: WritebackAction;
...@@ -41,11 +42,14 @@ function ActionParametersInputModal({ ...@@ -41,11 +42,14 @@ function ActionParametersInputModal({
action, action,
closeActionParametersModal, closeActionParametersModal,
}: Props) { }: Props) {
const title = getFormTitle(action);
return ( return (
<Modal onClose={closeActionParametersModal}> <Modal onClose={closeActionParametersModal}>
<ModalContent title={action.name} onClose={closeActionParametersModal}> <ModalContent title={title} onClose={closeActionParametersModal}>
<ActionParametersInputForm <ActionParametersInputForm
{...formProps} {...formProps}
action={action}
onSubmitSuccess={closeActionParametersModal} onSubmitSuccess={closeActionParametersModal}
/> />
</ModalContent> </ModalContent>
......
...@@ -79,7 +79,7 @@ function FormItem({ ...@@ -79,7 +79,7 @@ function FormItem({
<FormItemWrapper> <FormItemWrapper>
<FormItemName>{name}</FormItemName> <FormItemName>{name}</FormItemName>
<FormSettings> <FormSettings>
<FormField type={fieldSettings.inputType} /> <FormField tag={tag} fieldSettings={fieldSettings} />
<FieldSettingsPopover <FieldSettingsPopover
fieldSettings={fieldSettings} fieldSettings={fieldSettings}
onChange={onChange} onChange={onChange}
......
import React from "react"; import React from "react";
import { t } from "ttag"; import type { FieldSettings } from "metabase-types/api";
import type { InputType } from "metabase-types/api"; import type { TemplateTag } from "metabase-types/types/Query";
import Input from "metabase/core/components/Input"; import { getWidgetComponent } from "metabase/components/form/FormWidget";
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";
const getSampleOptions = () => [ import { getFormFieldForParameter } from "./utils";
{ name: t`Option One`, value: 1 },
{ name: t`Option Two`, value: 2 },
{ name: t`Option Three`, value: 3 },
];
// sample form fields // sample form fields
export function FormField({ type }: { type: InputType }) { export function FormField({
switch (type) { tag,
case "string": fieldSettings,
return <Input />; }: {
case "text": tag: TemplateTag;
return <FormTextArea />; fieldSettings: FieldSettings;
case "number": }) {
return <NumericInput />; const fieldProps = getFormFieldForParameter(tag, fieldSettings);
case "date": const InputField = getWidgetComponent(fieldProps);
return <DateInput />; return <InputField field={fieldProps} {...fieldProps} />;
case "dropdown":
return <Select options={getSampleOptions()} />;
case "inline-select":
return <Radio options={getSampleOptions()} variant="bubble" />;
default:
return <div>{t`no input selected`}</div>;
}
} }
...@@ -38,14 +38,14 @@ interface InputOptionsMap { ...@@ -38,14 +38,14 @@ interface InputOptionsMap {
} }
const getTextInputs = (): InputOptionType[] => [ const getTextInputs = (): InputOptionType[] => [
{
value: "text",
name: t`long text`,
},
{ {
value: "string", value: "string",
name: t`text`, name: t`text`,
}, },
{
value: "text",
name: t`long text`,
},
]; ];
const getSelectInputs = (): InputOptionType[] => [ const getSelectInputs = (): InputOptionType[] => [
......
export * from "./FormCreator"; export * from "./FormCreator";
export * from "./utils";
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 = ( export const getDefaultFormSettings = (
overrides: Partial<ActionFormSettings> = {}, overrides: Partial<ActionFormSettings> = {},
...@@ -25,3 +33,52 @@ export const getDefaultFieldSettings = ( ...@@ -25,3 +33,52 @@ export const getDefaultFieldSettings = (
width: "medium", width: "medium",
...overrides, ...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`;
...@@ -3,16 +3,22 @@ import { connect } from "react-redux"; ...@@ -3,16 +3,22 @@ import { connect } from "react-redux";
import { t } from "ttag"; import { t } from "ttag";
import Form from "metabase/containers/Form"; import Form from "metabase/containers/Form";
import {
getFormFieldForParameter,
getSubmitButtonLabel,
} from "metabase/writeback/components/ActionCreator/FormCreator";
import type { import type {
ArbitraryParameterForActionExecution, ArbitraryParameterForActionExecution,
WritebackParameter, WritebackParameter,
WritebackAction,
} from "metabase-types/api"; } from "metabase-types/api";
import type { Parameter, ParameterId } from "metabase-types/types/Parameter"; import type { Parameter, ParameterId } from "metabase-types/types/Parameter";
import type { Dispatch, ReduxAction } from "metabase-types/store"; import type { Dispatch, ReduxAction } from "metabase-types/store";
interface Props { interface Props {
missingParameters: WritebackParameter[]; missingParameters: WritebackParameter[];
action: WritebackAction;
onSubmit: (parameters: ArbitraryParameterForActionExecution[]) => ReduxAction; onSubmit: (parameters: ArbitraryParameterForActionExecution[]) => ReduxAction;
onSubmitSuccess: () => void; onSubmitSuccess: () => void;
dispatch: Dispatch; dispatch: Dispatch;
...@@ -26,24 +32,6 @@ function getActionParameterType(parameter: Parameter) { ...@@ -26,24 +32,6 @@ function getActionParameterType(parameter: Parameter) {
return type; 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( function formatParametersBeforeSubmit(
values: Record<ParameterId, string | number>, values: Record<ParameterId, string | number>,
missingParameters: WritebackParameter[], missingParameters: WritebackParameter[],
...@@ -68,15 +56,23 @@ function formatParametersBeforeSubmit( ...@@ -68,15 +56,23 @@ function formatParametersBeforeSubmit(
function ActionParametersInputForm({ function ActionParametersInputForm({
missingParameters, missingParameters,
action,
dispatch, dispatch,
onSubmit, onSubmit,
onSubmitSuccess, onSubmitSuccess,
}: Props) { }: Props) {
const fieldSettings = useMemo(
() => action.visualization_settings?.fields ?? {},
[action],
);
const form = useMemo(() => { const form = useMemo(() => {
return { return {
fields: missingParameters.map(getFormFieldForParameter), fields: missingParameters.map(param =>
getFormFieldForParameter(param, fieldSettings[param.id]),
),
}; };
}, [missingParameters]); }, [missingParameters, fieldSettings]);
const handleSubmit = useCallback( const handleSubmit = useCallback(
params => { params => {
...@@ -90,7 +86,11 @@ function ActionParametersInputForm({ ...@@ -90,7 +86,11 @@ function ActionParametersInputForm({
[missingParameters, onSubmit, onSubmitSuccess, dispatch], [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); export default connect()(ActionParametersInputForm);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment