-
Anton Kulyk authored
* Cherry-pick `ModelPicker` * Cherry-pick actions editor and form components * Migrate from entity forms * Remove `ModelPicker` * Temporarily add "New > Action" flow * Disable save button if query is empty * Fix utilities moved * Fix type errors * Address comments * Simplify `ActionForm` tests * Break down `ActionCreator` props * Add basic `ActionCreator` tests * update action creator header gap styling * Extract `convertActionToQuestionCard` * Fix `CreateActionForm` ignores action name * Fix `FormModelPicker` crash * Address comments * Remove "New > Action" flow Co-authored-by:
Ryan Laurie <iethree@gmail.com>
Anton Kulyk authored* Cherry-pick `ModelPicker` * Cherry-pick actions editor and form components * Migrate from entity forms * Remove `ModelPicker` * Temporarily add "New > Action" flow * Disable save button if query is empty * Fix utilities moved * Fix type errors * Address comments * Simplify `ActionForm` tests * Break down `ActionCreator` props * Add basic `ActionCreator` tests * update action creator header gap styling * Extract `convertActionToQuestionCard` * Fix `CreateActionForm` ignores action name * Fix `FormModelPicker` crash * Address comments * Remove "New > Action" flow Co-authored-by:
Ryan Laurie <iethree@gmail.com>
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ActionForm.tsx 5.94 KiB
import React, { useMemo } from "react";
import { t } from "ttag";
import _ from "underscore";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import type {
DraggableProvided,
OnDragEndResponder,
DroppableProvided,
} from "react-beautiful-dnd";
import type { FormikHelpers } from "formik";
import Button from "metabase/core/components/Button";
import Form from "metabase/core/components/Form";
import FormProvider from "metabase/core/components/FormProvider";
import FormSubmitButton from "metabase/core/components/FormSubmitButton";
import FormErrorMessage from "metabase/core/components/FormErrorMessage";
import Icon from "metabase/components/Icon";
import type {
ActionFormInitialValues,
ActionFormSettings,
FieldSettings,
WritebackParameter,
Parameter,
ParametersForActionExecution,
} from "metabase-types/api";
import { reorderFields } from "metabase/actions/containers/ActionCreator/FormCreator";
import { FieldSettingsButtons } from "../../containers/ActionCreator/FormCreator/FieldSettingsButtons";
import { FormFieldWidget } from "./ActionFormFieldWidget";
import {
ActionFormButtonContainer,
FormFieldContainer,
SettingsContainer,
InputContainer,
} from "./ActionForm.styled";
import { getForm, getFormValidationSchema } from "./utils";
export interface ActionFormComponentProps {
parameters: WritebackParameter[] | Parameter[];
initialValues?: ActionFormInitialValues;
onClose?: () => void;
onSubmit?: (
params: ParametersForActionExecution,
actions: FormikHelpers<ParametersForActionExecution>,
) => void;
submitTitle?: string;
submitButtonColor?: string;
formSettings?: ActionFormSettings;
setFormSettings?: (formSettings: ActionFormSettings) => void;
}
export const ActionForm = ({
parameters,
initialValues = {},
onClose,
onSubmit,
submitTitle,
submitButtonColor = "primary",
formSettings,
setFormSettings,
}: ActionFormComponentProps): JSX.Element => {
// allow us to change the color of the submit button
const submitButtonVariant = { [submitButtonColor]: true };
const isSettings = !!(formSettings && setFormSettings);
const form = useMemo(
() => getForm(parameters, formSettings?.fields),
[parameters, formSettings?.fields],
);
const formValidationSchema = useMemo(
() => getFormValidationSchema(parameters, formSettings?.fields),
[parameters, formSettings?.fields],
);
const handleDragEnd: OnDragEndResponder = ({ source, destination }) => {
if (!isSettings) {
return;
}
const oldOrder = source.index;
const newOrder = destination?.index ?? source.index;
const reorderedFields = reorderFields(
formSettings.fields,
oldOrder,
newOrder,
);
setFormSettings({
...formSettings,
fields: reorderedFields,
});
};
const handleChangeFieldSettings = (newFieldSettings: FieldSettings) => {
if (!isSettings || !newFieldSettings?.id) {
return;
}
setFormSettings({
...formSettings,
fields: {
...formSettings.fields,
[newFieldSettings.id]: newFieldSettings,
},
});
};
const handleSubmit = (
values: ParametersForActionExecution,
actions: FormikHelpers<ParametersForActionExecution>,
) => onSubmit?.(formValidationSchema.cast(values), actions);
if (isSettings) {
return (
<FormProvider
initialValues={initialValues}
validationSchema={formValidationSchema}
onSubmit={handleSubmit}
>
<Form role="form" data-testid="action-form-editor">
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="action-form-droppable">
{(provided: DroppableProvided) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
{form.fields.map((field, index) => (
<Draggable
key={`draggable-${field.name}`}
draggableId={field.name}
index={index}
>
{(provided: DraggableProvided) => (
<FormFieldContainer
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
isSettings={isSettings}
>
<SettingsContainer>
<Icon name="grabber2" size={14} />
</SettingsContainer>
<InputContainer>
<FormFieldWidget
key={field.name}
formField={field}
/>
</InputContainer>
<FieldSettingsButtons
fieldSettings={formSettings.fields[field.name]}
onChange={handleChangeFieldSettings}
/>
</FormFieldContainer>
)}
</Draggable>
))}
</div>
)}
</Droppable>
</DragDropContext>
</Form>
</FormProvider>
);
}
return (
<FormProvider
initialValues={initialValues}
validationSchema={formValidationSchema}
onSubmit={handleSubmit}
enableReinitialize
>
{({ dirty }) => (
<Form disabled={!dirty} role="form" data-testid="action-form">
{form.fields.map(field => (
<FormFieldWidget key={field.name} formField={field} />
))}
<ActionFormButtonContainer>
{onClose && <Button onClick={onClose}>{t`Cancel`}</Button>}
<FormSubmitButton
disabled={!dirty}
title={submitTitle ?? t`Save`}
{...submitButtonVariant}
/>
</ActionFormButtonContainer>
<FormErrorMessage />
</Form>
)}
</FormProvider>
);
};