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

Action Creator 9a: Simplify action click drill (#25579)

* simplify action click drill

* better validator type
parent f77d2ccb
No related branches found
No related tags found
No related merge requests found
Showing
with 317 additions and 278 deletions
......@@ -68,3 +68,13 @@ export type ArbitraryParameterForActionExecution =
ParameterForActionExecutionBase & {
target: ParameterTarget;
};
export interface ActionFormSubmitResult {
success: boolean;
message?: string;
error?: string;
}
export type OnSubmitActionForm = (
parameters: ArbitraryParameterForActionExecution[],
) => Promise<ActionFormSubmitResult>;
......@@ -10,6 +10,10 @@ export type BaseFieldValues = {
type FieldValidateResultOK = undefined | null | false;
type FieldValidateResultError = string;
export type Validator = (
value: string,
) => FieldValidateResultOK | FieldValidateResultError;
// Extending Record type here as field definition's props
// will be just spread to the final field widget
// (e.g. autoFocus, placeholder)
......@@ -31,7 +35,7 @@ export type BaseFieldDefinition = Record<string, unknown> & {
visibleIf?: Record<FieldName, unknown>;
initial?: () => DefaultFieldValue;
validate?: (value: any) => FieldValidateResultOK | FieldValidateResultError;
validate?: Validator;
normalize?: (value: any) => DefaultFieldValue;
};
......
......@@ -18,7 +18,7 @@ export type ParameterDimensionTarget = [
DimensionTarget | VariableTarget,
];
export type ParameterValueOrArray = string | Array<any>;
export type ParameterValueOrArray = string | number | Array<any>;
export type ParameterTarget =
| ParameterVariableTarget
......
......@@ -36,7 +36,10 @@ interface FormContainerProps<Values extends BaseFieldValues> {
initial?: () => void;
normalize?: () => void;
onSubmit: (values: Values) => void | Promise<void>;
onSubmit: (
values: Values,
formikHelpers?: FormikHelpers<Values>,
) => void | Promise<void>;
onSubmitSuccess?: (action: unknown) => void;
// various props
......@@ -218,8 +221,9 @@ function Form<Values extends BaseFieldValues>({
async (values: Values, formikHelpers: FormikHelpers<Values>) => {
try {
const normalized = formObject.normalize(values);
const result = await onSubmit(normalized);
const result = await onSubmit(normalized, formikHelpers);
onSubmitSuccess?.(result);
setError(null); // clear any previous errors
return result;
} catch (e) {
const error = handleError(e as ServerErrorResponse, formikHelpers);
......
......@@ -23,6 +23,8 @@ import type {
DashboardOrderedCard,
ActionDashboardCard,
ParameterMappedForActionExecution,
ArbitraryParameterForActionExecution,
ActionFormSubmitResult,
} from "metabase-types/api";
import type { Dispatch } from "metabase-types/store";
......@@ -233,44 +235,48 @@ export type ExecuteRowActionPayload = {
dashboard: Dashboard;
dashcard: ActionDashboardCard;
parameters: ParameterMappedForActionExecution[];
extra_parameters: ParameterMappedForActionExecution[];
extra_parameters: ArbitraryParameterForActionExecution[];
dispatch: Dispatch;
shouldToast?: boolean;
};
export const executeRowAction = ({
export const executeRowAction = async ({
dashboard,
dashcard,
parameters,
extra_parameters,
}: ExecuteRowActionPayload) => {
return async function (dispatch: any) {
try {
const result = await ActionsApi.execute({
dashboardId: dashboard.id,
dashcardId: dashcard.id,
parameters,
extra_parameters,
});
if (result["rows-affected"] > 0) {
dispatch(reloadDashboardCards());
dispatch(
addUndo({
toastColor: "success",
message: t`Successfully executed the action`,
}),
);
} else {
dispatch(
addUndo({
toastColor: "success",
message: t`Success! The action returned: ${JSON.stringify(result)}`,
}),
);
}
} catch (err) {
console.error(err);
const message =
(<any>err)?.data?.message ||
t`Something went wrong while executing the action`;
dispatch,
shouldToast = true,
}: ExecuteRowActionPayload): Promise<ActionFormSubmitResult> => {
let message = "";
try {
const result = await ActionsApi.execute({
dashboardId: dashboard.id,
dashcardId: dashcard.id,
parameters,
extra_parameters,
});
if (result["rows-affected"] > 0) {
dispatch(reloadDashboardCards());
message = t`Successfully executed the action`;
} else {
message = t`Success! The action returned: ${JSON.stringify(result)}`;
}
if (shouldToast) {
dispatch(
addUndo({
toastColor: "success",
message,
}),
);
}
return { success: true, message };
} catch (err) {
const message =
(<any>err)?.data?.message ||
t`Something went wrong while executing the action`;
if (shouldToast) {
dispatch(
addUndo({
icon: "warning",
......@@ -279,5 +285,6 @@ export const executeRowAction = ({
}),
);
}
};
return { success: false, error: message, message };
}
};
import { getDataFromClicked } from "metabase/lib/click-behavior";
import {
executeRowAction,
openActionParametersModal,
} from "metabase/dashboard/actions";
import { openActionParametersModal } from "metabase/dashboard/actions";
import type { ParameterMappedForActionExecution } from "metabase-types/api";
import type { ActionClickObject } from "./types";
import { prepareParameter, getNotProvidedActionParameters } from "./utils";
function ActionClickDrill({ clicked }: { clicked: ActionClickObject }) {
const { dashboard, dashcard } = clicked.extraData;
const { dashcard } = clicked.extraData;
const { onSubmit, missingParameters } = clicked;
const { action } = dashcard;
if (!action) {
return [];
}
const parameters: ParameterMappedForActionExecution[] = [];
const data = getDataFromClicked(clicked);
const parameterMappings = dashcard.parameter_mappings || [];
parameterMappings.forEach(mapping => {
const parameter = prepareParameter(mapping, { action, data });
if (parameter) {
parameters.push(parameter);
}
});
const missingParameters = getNotProvidedActionParameters(
action,
parameterMappings,
parameters,
);
function clickAction() {
if (missingParameters.length > 0) {
return openActionParametersModal({
dashcardId: dashcard.id,
props: {
missingParameters,
onSubmit: (extra_parameters: ParameterMappedForActionExecution[]) =>
executeRowAction({
dashboard,
dashcard,
parameters,
extra_parameters,
}),
onSubmit,
},
});
}
return executeRowAction({
dashboard,
dashcard,
parameters,
extra_parameters: [],
});
return onSubmit();
}
return [
......
......@@ -3,6 +3,7 @@ import * as dashboardActions from "metabase/dashboard/actions/writeback";
import type {
ActionParametersMapping,
DashboardOrderedCard,
ParameterMappedForActionExecution,
WritebackParameter,
} from "metabase-types/api";
import type { ParameterValueOrArray } from "metabase-types/types/Parameter";
......@@ -47,6 +48,10 @@ const PARAMETER_MAPPING: ActionParametersMapping = {
target: WRITEBACK_PARAMETER.target,
};
const dashboardParamterValues = {
[DASHBOARD_FILTER_PARAMETER.id]: DASHBOARD_FILTER_PARAMETER.value,
};
function getActionClickBehaviorData(value: any): ActionClickBehaviorData {
return {
column: {},
......@@ -69,16 +74,7 @@ describe("prepareParameter", () => {
parameters: [WRITEBACK_PARAMETER],
});
const parameter = prepareParameter(PARAMETER_MAPPING, {
data: {
column: {},
parameter: {},
parameterByName: {},
parameterBySlug: {},
userAttribute: {},
},
action,
});
const parameter = prepareParameter(PARAMETER_MAPPING, action, {});
expect(parameter).toBeUndefined();
});
......@@ -88,10 +84,11 @@ describe("prepareParameter", () => {
parameters: [],
});
const parameter = prepareParameter(PARAMETER_MAPPING, {
data: getActionClickBehaviorData(DASHBOARD_FILTER_PARAMETER.value),
const parameter = prepareParameter(
PARAMETER_MAPPING,
action,
});
dashboardParamterValues,
);
expect(parameter).toBeUndefined();
});
......@@ -101,10 +98,11 @@ describe("prepareParameter", () => {
parameters: [WRITEBACK_PARAMETER],
});
const parameter = prepareParameter(PARAMETER_MAPPING, {
data: getActionClickBehaviorData(DASHBOARD_FILTER_PARAMETER.value),
const parameter = prepareParameter(
PARAMETER_MAPPING,
action,
});
dashboardParamterValues,
);
expect(parameter).toEqual({
id: DASHBOARD_FILTER_PARAMETER.id,
......@@ -119,10 +117,11 @@ describe("prepareParameter", () => {
parameters: [WRITEBACK_PARAMETER],
});
const parameter = prepareParameter(PARAMETER_MAPPING, {
data: getActionClickBehaviorData([DASHBOARD_FILTER_PARAMETER.value]),
const parameter = prepareParameter(
PARAMETER_MAPPING,
action,
});
dashboardParamterValues,
);
expect(parameter).toEqual({
id: DASHBOARD_FILTER_PARAMETER.id,
......@@ -137,26 +136,21 @@ describe("getNotProvidedActionParameters", () => {
it("returns empty list if no parameters passed", () => {
const action = createMockQueryAction();
const result = getNotProvidedActionParameters(action, [], []);
const result = getNotProvidedActionParameters(action, []);
expect(result).toHaveLength(0);
});
it("returns empty list if all parameters have values", () => {
const action = createMockQueryAction({ parameters: [WRITEBACK_PARAMETER] });
const result = getNotProvidedActionParameters(
action,
[PARAMETER_MAPPING],
[
{
id: DASHBOARD_FILTER_PARAMETER.id,
value: 5,
type: "number",
target: WRITEBACK_ARBITRARY_PARAMETER.target,
},
],
);
const result = getNotProvidedActionParameters(action, [
{
id: DASHBOARD_FILTER_PARAMETER.id,
value: 5,
type: "number",
target: WRITEBACK_PARAMETER.target,
},
]);
expect(result).toHaveLength(0);
});
......@@ -166,42 +160,14 @@ describe("getNotProvidedActionParameters", () => {
parameters: [WRITEBACK_PARAMETER, WRITEBACK_ARBITRARY_PARAMETER],
});
const result = getNotProvidedActionParameters(
action,
[PARAMETER_MAPPING],
[
{
id: DASHBOARD_FILTER_PARAMETER.id,
value: 5,
type: "number",
target: WRITEBACK_ARBITRARY_PARAMETER.target,
},
],
);
expect(result).toEqual([WRITEBACK_ARBITRARY_PARAMETER]);
});
it("returns mapped parameters without value", () => {
const action = createMockQueryAction({
parameters: [WRITEBACK_PARAMETER],
});
const result = getNotProvidedActionParameters(
action,
[PARAMETER_MAPPING],
[
{
id: DASHBOARD_FILTER_PARAMETER.id,
type: "number",
target: WRITEBACK_PARAMETER.target,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
value: undefined,
},
],
);
const result = getNotProvidedActionParameters(action, [
{
id: DASHBOARD_FILTER_PARAMETER.id,
value: 5,
type: "number",
target: WRITEBACK_ARBITRARY_PARAMETER.target,
},
]);
expect(result).toEqual([WRITEBACK_PARAMETER]);
});
......@@ -211,7 +177,7 @@ describe("getNotProvidedActionParameters", () => {
parameters: [{ ...WRITEBACK_PARAMETER, default: 10 }],
});
const result = getNotProvidedActionParameters(action, [], []);
const result = getNotProvidedActionParameters(action, []);
expect(result).toHaveLength(0);
});
......@@ -223,42 +189,38 @@ describe("ActionClickDrill", () => {
});
function setup({
actionParameters = [],
dashboardParameters = [],
parameterMappings = [],
parameterValuesBySlug = {},
missingParameters = [],
}: {
actionParameters?: WritebackParameter[];
dashboardParameters?: UiParameter[];
parameterMappings?: ActionParametersMapping[];
parameterValuesBySlug?: Record<string, { value: ParameterValueOrArray }>;
missingParameters?: WritebackParameter[];
} = {}) {
const executeActionSpy = jest.spyOn(dashboardActions, "executeRowAction");
const submitSpy = jest.fn();
const openActionParametersModalSpy = jest.spyOn(
dashboardActions,
"openActionParametersModal",
);
const action = createMockQueryAction({ parameters: actionParameters });
const action = createMockQueryAction();
const dashcard = createMockDashboardActionButton({
action,
action_id: action.id,
parameter_mappings: parameterMappings,
});
const dashboard = createMockDashboard({
ordered_cards: [dashcard as unknown as DashboardOrderedCard],
parameters: dashboardParameters,
});
const onSubmit = jest.fn();
const clickActions = ActionClickDrill({
clicked: {
data: [],
extraData: {
dashboard,
dashcard,
parameterValuesBySlug,
parameterValuesBySlug: {},
userAttributes: [],
},
missingParameters,
onSubmit: submitSpy,
},
});
......@@ -267,37 +229,20 @@ describe("ActionClickDrill", () => {
dashboard,
dashcard,
clickActions,
executeActionSpy,
submitSpy,
openActionParametersModalSpy,
};
}
it("executes action correctly", () => {
const { clickActions, dashboard, dashcard, executeActionSpy } = setup({
actionParameters: [WRITEBACK_PARAMETER],
dashboardParameters: [DASHBOARD_FILTER_PARAMETER],
parameterMappings: [PARAMETER_MAPPING],
parameterValuesBySlug: {
[DASHBOARD_FILTER_PARAMETER.slug]: DASHBOARD_FILTER_PARAMETER.value,
},
it("executes action without missing parameters correctly", () => {
const { clickActions, dashboard, dashcard, submitSpy } = setup({
missingParameters: [],
});
const [clickAction] = clickActions;
clickAction.action();
expect(executeActionSpy).toBeCalledWith({
dashboard,
dashcard,
parameters: [
{
id: DASHBOARD_FILTER_PARAMETER.id,
type: WRITEBACK_PARAMETER.type,
value: DASHBOARD_FILTER_PARAMETER.value,
target: WRITEBACK_PARAMETER.target,
},
],
extra_parameters: [],
});
expect(submitSpy).toHaveBeenCalled();
});
it("executes action with arbitrary parameters correctly", () => {
......@@ -305,22 +250,17 @@ describe("ActionClickDrill", () => {
clickActions,
dashboard,
dashcard,
executeActionSpy,
submitSpy,
openActionParametersModalSpy,
} = setup({
actionParameters: [WRITEBACK_PARAMETER, WRITEBACK_ARBITRARY_PARAMETER],
dashboardParameters: [DASHBOARD_FILTER_PARAMETER],
parameterMappings: [PARAMETER_MAPPING],
parameterValuesBySlug: {
[DASHBOARD_FILTER_PARAMETER.slug]: DASHBOARD_FILTER_PARAMETER.value,
},
missingParameters: [WRITEBACK_ARBITRARY_PARAMETER],
});
clickActions[0].action();
// Ensure we're not trying to execute the action immediately
// until we collect the arbitrary parameter value from a user
expect(executeActionSpy).not.toHaveBeenCalled();
expect(submitSpy).not.toHaveBeenCalled();
// Emulate ActionParameterInputForm submission
const { props } = openActionParametersModalSpy.mock.calls[0][0];
......@@ -332,25 +272,13 @@ describe("ActionClickDrill", () => {
},
]);
expect(executeActionSpy).toHaveBeenCalledWith({
dashboard,
dashcard,
parameters: [
{
id: DASHBOARD_FILTER_PARAMETER.id,
type: WRITEBACK_PARAMETER.type,
value: DASHBOARD_FILTER_PARAMETER.value,
target: WRITEBACK_PARAMETER.target,
},
],
extra_parameters: [
{
target: WRITEBACK_ARBITRARY_PARAMETER.target,
value: 123,
type: WRITEBACK_ARBITRARY_PARAMETER.type,
},
],
});
expect(submitSpy).toHaveBeenCalledWith([
{
target: WRITEBACK_ARBITRARY_PARAMETER.target,
value: 123,
type: WRITEBACK_ARBITRARY_PARAMETER.type,
},
]);
});
it("does nothing for buttons without linked action", () => {
......@@ -374,6 +302,8 @@ describe("ActionClickDrill", () => {
},
userAttributes: [],
},
onSubmit: jest.fn(),
missingParameters: [],
},
});
......
import type { ActionDashboardCard, Dashboard } from "metabase-types/api";
import type {
ActionDashboardCard,
Dashboard,
ParameterMappedForActionExecution,
WritebackParameter,
} from "metabase-types/api";
import type { Column } from "metabase-types/types/Dataset";
import type {
ParameterId,
......@@ -16,6 +21,10 @@ type ActionClickExtraData = {
export type ActionClickObject = Omit<ClickObject, "extraData"> & {
data: any;
extraData: ActionClickExtraData;
onSubmit: () => (
parameters: ParameterMappedForActionExecution[],
) => Promise<boolean>;
missingParameters: WritebackParameter[];
};
export type ActionClickBehaviorData = {
......
import _ from "underscore";
import { isEmpty } from "metabase/lib/validate";
import type {
ActionDashboardCard,
ActionParametersMapping,
ParameterMappedForActionExecution,
WritebackAction,
WritebackParameter,
} from "metabase-types/api";
import type {
ParameterTarget,
ParameterValueOrArray,
} from "metabase-types/types/Parameter";
import type { ActionClickBehaviorData } from "./types";
import type { ParameterValueOrArray } from "metabase-types/types/Parameter";
function formatParameterValue(value: ParameterValueOrArray) {
return Array.isArray(value) ? value[0] : value;
}
export function getDashcardParamValues(
dashcard: ActionDashboardCard,
parameterValues: { [id: string]: ParameterValueOrArray },
) {
if (!dashcard.action || !dashcard?.parameter_mappings?.length) {
return [];
}
const { action, parameter_mappings } = dashcard;
return parameter_mappings
.map(mapping => prepareParameter(mapping, action, parameterValues))
.filter(Boolean) as ParameterMappedForActionExecution[];
}
export function prepareParameter(
mapping: ActionParametersMapping,
{
data,
action,
}: {
data: ActionClickBehaviorData;
action: WritebackAction;
},
action: WritebackAction,
parameterValues: { [id: string]: ParameterValueOrArray },
) {
const { parameter_id: sourceParameterId, target: actionParameterTarget } =
mapping;
const sourceParameter = data.parameter[sourceParameterId];
const parameterValue = parameterValues[sourceParameterId];
const actionParameter = action.parameters.find(parameter =>
_.isEqual(parameter.target, actionParameterTarget),
);
if (!actionParameter || !sourceParameter) {
// dont return unmapped or empty values
if (!actionParameter || isEmpty(parameterValue)) {
return;
}
return {
id: sourceParameterId,
type: actionParameter.type,
value: formatParameterValue(sourceParameter.value),
value: formatParameterValue(parameterValue),
target: actionParameterTarget,
};
}
function isMappedParameter(
parameter: WritebackParameter,
parameterMappings: ActionParametersMapping[],
parameterMappings: ParameterMappedForActionExecution[],
) {
return parameterMappings.some(mapping =>
_.isEqual(mapping.target, parameter.target),
......@@ -58,25 +65,13 @@ function isMappedParameter(
export function getNotProvidedActionParameters(
action: WritebackAction,
parameterMappings: ActionParametersMapping[],
mappedParameters: ParameterMappedForActionExecution[],
dashboardParamValues: ParameterMappedForActionExecution[],
) {
const emptyParameterTargets: ParameterTarget[] = [];
mappedParameters.forEach(mapping => {
if (mapping.value === undefined) {
emptyParameterTargets.push(mapping.target);
}
});
// return any action parameters that don't have mapped values
return action.parameters.filter(parameter => {
if ("default" in parameter) {
return false;
}
const isNotMapped = !isMappedParameter(parameter, parameterMappings);
const isMappedButNoValue = emptyParameterTargets.some(target =>
_.isEqual(target, parameter.target),
);
return isNotMapped || isMappedButNoValue;
return !isMappedParameter(parameter, dashboardParamValues);
});
}
......@@ -9,12 +9,13 @@ import type {
import validate from "metabase/lib/validate";
import type { Parameter } from "metabase-types/types/Parameter";
import type { TemplateTag } from "metabase-types/types/Query";
import type { Validator } from "metabase-types/forms";
export const getDefaultFormSettings = (
overrides: Partial<ActionFormSettings> = {},
): ActionFormSettings => ({
name: "",
type: "inline",
type: "modal",
description: "",
fields: {},
confirmMessage: "",
......@@ -68,14 +69,12 @@ const fieldPropsTypeMap: FieldPropTypeMap = {
const inputTypeHasOptions = (fieldSettings: FieldSettings) =>
["dropdown", "inline-select"].includes(fieldSettings.inputType);
type validator = (...args: (string | number)[]) => string | void;
interface FieldProps {
type: string;
options?: OptionType[];
values?: any;
placeholder?: string;
validate?: validator;
validate?: Validator;
}
const getParameterFieldProps = (fieldSettings: FieldSettings) => {
......
import React from "react";
import React, { useMemo, useCallback } from "react";
import { t } from "ttag";
import { connect } from "react-redux";
import { isImplicitActionButton } from "metabase/writeback/utils";
import type { ActionDashboardCard, Dashboard } from "metabase-types/api";
import type {
ActionDashboardCard,
ArbitraryParameterForActionExecution,
ActionParametersMapping,
Dashboard,
ParameterMappedForActionExecution,
WritebackQueryAction,
} from "metabase-types/api";
import type { VisualizationProps } from "metabase-types/types/Visualization";
import type { Dispatch } from "metabase-types/store";
import type { ParameterValueOrArray } from "metabase-types/types/Parameter";
import {
getDashcardParamValues,
getNotProvidedActionParameters,
prepareParameter,
} from "metabase/modes/components/drill/ActionClickDrill/utils";
import { executeRowAction } from "metabase/dashboard/actions";
import { StyledButton } from "./ActionButton.styled";
import DefaultActionButton from "./DefaultActionButton";
import ImplicitActionButton from "./ImplicitActionButton";
......@@ -11,13 +30,77 @@ import ImplicitActionButton from "./ImplicitActionButton";
interface ActionProps extends VisualizationProps {
dashcard: ActionDashboardCard;
dashboard: Dashboard;
dispatch: Dispatch;
parameterValues: { [id: string]: ParameterValueOrArray };
}
function Action({ dashcard, ...props }: ActionProps) {
function ActionComponent({
dashcard,
dashboard,
dispatch,
isSettings,
settings,
getExtraDataForClick,
onVisualizationClick,
parameterValues,
}: ActionProps) {
const dashcardParamValues = useMemo(
() => getDashcardParamValues(dashcard, parameterValues),
[dashcard, parameterValues],
);
const missingParameters = useMemo(() => {
if (!dashcard.action) {
return [];
}
return getNotProvidedActionParameters(
dashcard.action,
dashcardParamValues ?? [],
);
}, [dashcard, dashcardParamValues]);
const onSubmit = useCallback(
(extra_parameters: ArbitraryParameterForActionExecution[]) =>
executeRowAction({
dashboard,
dashcard,
parameters: dashcardParamValues,
extra_parameters,
dispatch,
shouldToast: true,
}),
[dashboard, dashcard, dashcardParamValues, dispatch],
);
if (isImplicitActionButton(dashcard)) {
return <ImplicitActionButton {...props} />;
return <ImplicitActionButton isSettings={isSettings} settings={settings} />;
}
return <DefaultActionButton {...props} />;
return (
<DefaultActionButton
onSubmit={onSubmit}
missingParameters={missingParameters}
isSettings={isSettings}
settings={settings}
getExtraDataForClick={getExtraDataForClick}
onVisualizationClick={onVisualizationClick}
/>
);
}
export default Action;
const ConnectedActionComponent = connect()(ActionComponent);
export default function Action(props: ActionProps) {
if (
!props.dashcard?.action &&
!props.dashcard?.visualization_settings?.click_behavior
) {
return (
<StyledButton>
<strong>{t`Assign an action`}</strong>
</StyledButton>
);
}
return <ConnectedActionComponent {...props} />;
}
import React, { useCallback, useMemo } from "react";
import type { Dashboard } from "metabase-types/api";
import type { VisualizationProps } from "metabase-types/types/Visualization";
import type {
OnSubmitActionForm,
WritebackParameter,
} from "metabase-types/api";
import ActionButtonView from "./ActionButtonView";
interface DefaultActionButtonProps extends VisualizationProps {
dashboard: Dashboard;
interface DefaultActionButtonProps {
isSettings: VisualizationProps["isSettings"];
settings: VisualizationProps["settings"];
getExtraDataForClick: VisualizationProps["getExtraDataForClick"];
onVisualizationClick: VisualizationProps["onVisualizationClick"];
onSubmit: OnSubmitActionForm;
missingParameters: WritebackParameter[];
}
function DefaultActionButton({
......@@ -14,8 +22,13 @@ function DefaultActionButton({
settings,
getExtraDataForClick,
onVisualizationClick,
onSubmit,
missingParameters,
}: DefaultActionButtonProps) {
const clickObject = useMemo(() => ({ settings }), [settings]);
const clickObject = useMemo(
() => ({ settings, onSubmit, missingParameters }),
[settings, onSubmit, missingParameters],
);
const extraData = useMemo(
() => getExtraDataForClick?.(clickObject),
......
......@@ -3,7 +3,6 @@ import React from "react";
import { useToggle } from "metabase/hooks/use-toggle";
import type {
Dashboard,
ImplicitActionType,
ImplicitActionClickBehavior,
} from "metabase-types/api";
......@@ -15,9 +14,10 @@ import ImplicitDeleteModal from "./ImplicitDeleteModal";
import ActionButtonView from "./ActionButtonView";
interface ImplicitActionButtonProps extends VisualizationProps {
dashboard: Dashboard;
}
type ImplicitActionButtonProps = {
isSettings: VisualizationProps["isSettings"];
settings: VisualizationProps["settings"];
};
function FallbackActionComponent({ children }: { children: React.ReactNode }) {
return children;
......
import React, { useCallback, useMemo } from "react";
import { connect } from "react-redux";
import Form from "metabase/containers/Form";
import Form from "metabase/containers/FormikForm";
import {
getFormFieldForParameter,
getSubmitButtonLabel,
} from "metabase/writeback/components/ActionCreator/FormCreator";
import type {
ArbitraryParameterForActionExecution,
WritebackParameter,
WritebackAction,
WritebackQueryAction,
OnSubmitActionForm,
} from "metabase-types/api";
import type { Dispatch, ReduxAction } from "metabase-types/store";
import type { FormFieldDefinition } from "metabase-types/forms";
import { formatParametersBeforeSubmit, setDefaultValues } from "./utils";
interface Props {
missingParameters: WritebackParameter[];
action: WritebackAction;
onSubmit: (parameters: ArbitraryParameterForActionExecution[]) => ReduxAction;
onSubmitSuccess: () => void;
dispatch: Dispatch;
action: WritebackQueryAction;
onSubmit: OnSubmitActionForm;
onSubmitSuccess?: () => void;
}
function ActionParametersInputForm({
missingParameters,
action,
dispatch,
onSubmit,
onSubmitSuccess,
}: Props) {
......@@ -37,33 +33,55 @@ function ActionParametersInputForm({
[action],
);
const formParams = useMemo(
() => missingParameters ?? Object.values(action.parameters) ?? [],
[missingParameters, action],
);
const form = useMemo(() => {
return {
fields: missingParameters.map(param =>
fields: formParams?.map(param =>
getFormFieldForParameter(param, fieldSettings[param.id]),
),
};
}, [missingParameters, fieldSettings]);
}, [formParams, fieldSettings]);
const handleSubmit = useCallback(
params => {
async (params, actions) => {
actions.setSubmitting(true);
const paramsWithDefaultValues = setDefaultValues(params, fieldSettings);
const formattedParams = formatParametersBeforeSubmit(
paramsWithDefaultValues,
missingParameters,
formParams,
);
dispatch(onSubmit(formattedParams));
onSubmitSuccess();
const { success, error } = await onSubmit(formattedParams);
if (success) {
actions.setErrors({});
onSubmitSuccess?.();
actions.resetForm();
} else {
throw new Error(error);
}
},
[missingParameters, onSubmit, onSubmitSuccess, dispatch, fieldSettings],
[onSubmit, onSubmitSuccess, fieldSettings, formParams],
);
const initialValues = useMemo(
() => Object.fromEntries(form.fields.map(field => [field.name, ""])),
[form],
);
const submitButtonLabel = getSubmitButtonLabel(action);
return (
<Form form={form} onSubmit={handleSubmit} submitTitle={submitButtonLabel} />
<Form
form={form}
initialValues={initialValues}
onSubmit={handleSubmit}
submitTitle={submitButtonLabel}
/>
);
}
export default connect()(ActionParametersInputForm);
export default 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