Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update auto component error messages #643

Merged
merged 1 commit into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions packages/react/spec/auto/PolarisAutoForm.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { UserEvent } from "@testing-library/user-event";
import { userEvent } from "@testing-library/user-event";
import type { ReactNode } from "react";
import React from "react";
import { TriggerableActionRequiredErrorMessage } from "../../src/auto/AutoFormActionValidators.js";
import { MissingApiTriggerErrorMessage } from "../../src/auto/AutoFormActionValidators.js";
import { PolarisAutoForm } from "../../src/auto/polaris/PolarisAutoForm.js";
import { PolarisAutoInput } from "../../src/auto/polaris/inputs/PolarisAutoInput.js";
import { PolarisAutoSubmit } from "../../src/auto/polaris/submit/PolarisAutoSubmit.js";
Expand Down Expand Up @@ -676,28 +676,28 @@ describe("PolarisAutoForm", () => {
it("throws an error when a model action without triggers", () => {
expect(() => {
render(<PolarisAutoForm action={api.autoTableTest.noTriggerAction as any} />, { wrapper: PolarisMockedProviders });
}).toThrow(TriggerableActionRequiredErrorMessage);
}).toThrow(MissingApiTriggerErrorMessage);
});

it("throws an error when a global action without triggers", () => {
expect(() => {
render(<PolarisAutoForm action={api.noTriggerGlobalAction as any} />, { wrapper: PolarisMockedProviders });
}).toThrow(TriggerableActionRequiredErrorMessage);
}).toThrow(MissingApiTriggerErrorMessage);
});
});
describe("Has triggers in api client but no triggers in action metadata", () => {
it("throws an error when a model action without triggers", () => {
expect(() => {
render(<PolarisAutoForm action={api.widget.create as any} />, { wrapper: PolarisMockedProviders });
loadMockWidgetCreateMetadata({ triggers: [{ specID: "non/api/trigger", __typename: "GadgetTrigger" }] });
}).toThrow(TriggerableActionRequiredErrorMessage);
}).toThrow(MissingApiTriggerErrorMessage);
});

it("throws an error when a global action without triggers", () => {
expect(() => {
render(<PolarisAutoForm action={api.flipAll as any} />, { wrapper: PolarisMockedProviders });
loadMockFlipAllMetadata({ triggers: [{ specID: "non/api/trigger", __typename: "GadgetTrigger" }] });
}).toThrow(TriggerableActionRequiredErrorMessage);
}).toThrow(MissingApiTriggerErrorMessage);
});
});
});
Expand Down
10 changes: 10 additions & 0 deletions packages/react/spec/auto/form/PolarisAutoFormErrors.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ export default {
],
};

export const InvalidModelAction = {
args: {
action: api.autoTableTest.invalidAction,
},
};

export const MissingActionProp = {
args: {},
};

export const FieldNameCustomParamCollisionError = {
args: {
findBy: "1",
Expand Down
11 changes: 11 additions & 0 deletions packages/react/spec/auto/table/PolarisAutoTable.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { act, render } from "@testing-library/react";
import type { UserEvent } from "@testing-library/user-event";
import { userEvent } from "@testing-library/user-event";
import React from "react";
import { InvalidModelErrorMessage } from "../../../src/auto/AutoTableValidators.js";
import { GadgetFieldType } from "../../../src/internal/gql/graphql.js";
import type { TableColumn } from "../../../src/use-table/types.js";
import { testApi as api } from "../../apis.js";
Expand Down Expand Up @@ -451,4 +452,14 @@ describe("PolarisAutoTable", () => {
expect(firstRowCells[1].innerHTML).not.toContain("...");
});
});

describe("invalid model", () => {
it("throws an error when the model is not valid", () => {
expect(() => {
render(<PolarisAutoTable model={(api as any).invalidModel} />, {
wrapper: PolarisMockedProviders,
});
}).toThrow(InvalidModelErrorMessage);
});
});
});
21 changes: 18 additions & 3 deletions packages/react/src/auto/AutoFormActionValidators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ActionFunction, GlobalActionFunction } from "@gadgetinc/api-client-core";
import type { ActionMetadata, GlobalActionMetadata } from "../metadata.js";
import type { useAutoForm } from "./AutoForm.js";

export const validateNonBulkAction = (action: ActionFunction<any, any, any, any, any> | GlobalActionFunction<any>) => {
if (action.isBulk) {
Expand All @@ -8,14 +9,28 @@ export const validateNonBulkAction = (action: ActionFunction<any, any, any, any,
};

const GadgetApiTriggerSpecId = "gadget/trigger/graphql_api";
export const TriggerableActionRequiredErrorMessage = `"action" must be a valid Gadget action with an API trigger to be used in AutoForms`;

export const MissingActionPropErrorMessage = "Specify a valid Gagdet action to use AutoForm";
export const InvalidActionErrorMessage = `"action" is not a valid Gadget action`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: maybe mention here that the action is used in AutoForm

export const MissingApiTriggerErrorMessage = `"action" requires an API trigger to be used in AutoForm`;

const validActionTypes = ["globalAction", "action"];

export const validateTriggersFromApiClient = (action: ActionFunction<any, any, any, any, any> | GlobalActionFunction<any>) => {
if (!validActionTypes.includes(action.type)) {
// When the API client is built with an action without the API trigger, the type will be "stubbedAction"
// action.type === "globalAction" | "action" // Only when the action has the API trigger when the api client is built
throw new Error(TriggerableActionRequiredErrorMessage);
throw new Error(MissingApiTriggerErrorMessage);
}
};

export const validateAutoFormProps = (props: Parameters<typeof useAutoForm>[0]) => {
if (!("action" in props)) {
throw new Error(MissingActionPropErrorMessage);
}

if (!props.action) {
throw new Error(InvalidActionErrorMessage);
}
};

Expand All @@ -32,7 +47,7 @@ export const validateTriggersFromMetadata = (metadata?: ActionMetadata | GlobalA

const hasApiTrigger = triggersAsArray.some((trigger) => trigger.specID === GadgetApiTriggerSpecId);
if (!hasApiTrigger) {
throw new Error(TriggerableActionRequiredErrorMessage);
throw new Error(MissingApiTriggerErrorMessage);
}
}
};
9 changes: 9 additions & 0 deletions packages/react/src/auto/AutoTableValidators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { useTable } from "../useTable.js";

export const InvalidModelErrorMessage = `"model" is not a valid Gadget model`;
alanko0511 marked this conversation as resolved.
Show resolved Hide resolved

export const validateAutoTableProps = (manager: Parameters<typeof useTable>[0]) => {
if (!manager) {
throw new Error(InvalidModelErrorMessage);
}
};
3 changes: 3 additions & 0 deletions packages/react/src/auto/hooks/useTableBulkActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useMemo } from "react";
import deepEqual from "react-fast-compare";
import type { ActionCallback, TableOptions } from "../../use-table/types.js";
import { humanizeCamelCase } from "../../utils.js";
import { validateAutoTableProps } from "../AutoTableValidators.js";

export type ModelActionDetails =
| {
Expand Down Expand Up @@ -32,6 +33,8 @@ export const useTableBulkActions = (props: {
}) => {
const { model, actions, excludeActions } = props;

validateAutoTableProps(props.model);

if (actions && excludeActions) {
throw new Error("Cannot have both actions and excludeActions in the same table");
}
Expand Down
7 changes: 3 additions & 4 deletions packages/react/src/auto/mui/MUIAutoForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React from "react";
import { FormProvider } from "react-hook-form";
import { humanizeCamelCase, type OptionsType } from "../../utils.js";
import { useAutoForm, type AutoFormProps } from "../AutoForm.js";
import { TriggerableActionRequiredErrorMessage } from "../AutoFormActionValidators.js";
import { validateAutoFormProps } from "../AutoFormActionValidators.js";
import { AutoFormMetadataContext } from "../AutoFormContext.js";
import { MUIAutoInput } from "./inputs/MUIAutoInput.js";
import { MUIAutoSubmit } from "./submit/MUIAutoSubmit.js";
Expand Down Expand Up @@ -36,9 +36,7 @@ export const MUIAutoForm = <
) => {
const { action, findBy } = props as MUIAutoFormProps<GivenOptions, SchemaT, ActionFunc> & { findBy: any };

if (!action) {
throw new Error(TriggerableActionRequiredErrorMessage);
}
validateAutoFormProps(props);

// Component key to force re-render when the action or findBy changes
const componentKey = `${action.modelApiIdentifier ?? ""}.${action.operationName}.${findBy}`;
Expand All @@ -53,6 +51,7 @@ export const MUIAutoFormComponent = <
props: MUIAutoFormProps<GivenOptions, SchemaT, ActionFunc>
) => {
const { record: _record, action, findBy, ...rest } = props as MUIAutoFormProps<GivenOptions, SchemaT, ActionFunc> & { findBy: any };

const { metadata, fetchingMetadata, metadataError, fields, submit, formError, isSubmitting, isSubmitSuccessful, originalFormMethods } =
useAutoForm(props);

Expand Down
6 changes: 2 additions & 4 deletions packages/react/src/auto/polaris/PolarisAutoForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { FormProvider } from "react-hook-form";
import { humanizeCamelCase, type OptionsType } from "../../utils.js";
import type { AutoFormProps } from "../AutoForm.js";
import { useAutoForm } from "../AutoForm.js";
import { TriggerableActionRequiredErrorMessage } from "../AutoFormActionValidators.js";
import { validateAutoFormProps } from "../AutoFormActionValidators.js";
import { AutoFormMetadataContext } from "../AutoFormContext.js";
import { PolarisAutoInput } from "./inputs/PolarisAutoInput.js";
import { PolarisAutoSubmit } from "./submit/PolarisAutoSubmit.js";
Expand All @@ -33,9 +33,7 @@ export const PolarisAutoForm = <
const { action, findBy } = props as AutoFormProps<GivenOptions, SchemaT, ActionFunc> &
Omit<Partial<FormProps>, "action"> & { findBy: any };

if (!action) {
throw new Error(TriggerableActionRequiredErrorMessage);
}
validateAutoFormProps(props);

// Component key to force re-render when the action or findBy changes
const componentKey = `${action.modelApiIdentifier ?? ""}.${action.operationName}.${findBy}`;
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/auto/polaris/PolarisAutoTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useTable } from "../../useTable.js";
import type { ColumnValueType, OptionsType } from "../../utils.js";
import type { AutoTableProps } from "../AutoTable.js";
import { AutoTableContext } from "../AutoTableContext.js";
import { validateAutoTableProps } from "../AutoTableValidators.js";
import type { BulkActionOption } from "../hooks/useTableBulkActions.js";
import { useTableBulkActions } from "../hooks/useTableBulkActions.js";
import { PolarisAutoBulkActionModal } from "./PolarisAutoBulkActionModal.js";
Expand Down Expand Up @@ -65,6 +66,9 @@ export const PolarisAutoTable = <
props: AutoTableProps<GivenOptions, SchemaT, FinderFunction, Options>
) => {
const { model } = props;

validateAutoTableProps(props.model);

const componentKey = `${[model.findMany.namespace, model.findMany.modelApiIdentifier].join("_")}AutoTable`;

return <PolarisAutoTableComponent key={componentKey} {...props} />;
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/useTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type Select,
} from "@gadgetinc/api-client-core";
import { useCallback, useMemo, useState } from "react";
import { validateAutoTableProps } from "./auto/AutoTableValidators.js";
import { useModelMetadata } from "./metadata.js";
import { getTableColumns, getTableRows, getTableSelectionMap, getTableSpec } from "./use-table/helpers.js";
import type { TableOptions, TableResult } from "./use-table/types.js";
Expand Down Expand Up @@ -48,6 +49,8 @@ export const useTable = <
): TableResult<
GadgetRecord<Select<Exclude<F["schemaType"], null | undefined>, DefaultSelection<F["selectionType"], Options, F["defaultSelection"]>>>[]
> => {
validateAutoTableProps(manager);

const namespace = manager.findMany.namespace;
const namespaceAsArray: string[] = namespace ? (Array.isArray(namespace) ? namespace : [namespace]) : [];
const {
Expand Down
Loading