Skip to content

Commit 26fc774

Browse files
committed
Surface configuration and sdk errors
1 parent 9b22d10 commit 26fc774

File tree

9 files changed

+99
-31
lines changed

9 files changed

+99
-31
lines changed

packages/connect-react/examples/nextjs/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/connect-react/examples/nextjs/src/app/page.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { fetchToken } from "./actions";
99

1010
export default function Home() {
11-
const userId = "my-authed-user-id";
11+
const userId = "demo-32aaf9cd-5df0-4ffe-86b5-db43a61a842c"; //gmail-new-email-received
1212
const client = createFrontendClient({
1313
environment: "development",
1414
externalUserId: userId,
@@ -26,6 +26,11 @@ export default function Home() {
2626
setDynamicPropsId,
2727
] = useState<string | undefined>();
2828

29+
const [
30+
errors,
31+
setErrors,
32+
] = useState<unknown[] | unknown | undefined>(undefined);
33+
2934
const handleDynamicProps = (dynamicProps: { id: string | undefined }) => {
3035
setDynamicPropsId(dynamicProps.id)
3136
}
@@ -36,19 +41,22 @@ export default function Home() {
3641
<FrontendClientProvider client={client}>
3742
<ComponentFormContainer
3843
userId={userId}
39-
componentKey="slack-send-message"
44+
componentKey="gmail-new-email-received"
4045
configuredProps={configuredProps}
4146
onUpdateDynamicProps={handleDynamicProps}
4247
onUpdateConfiguredProps={setConfiguredProps}
48+
sdkErrors={errors}
4349
onSubmit={async () => {
4450
try {
45-
await client.actionRun({
51+
const result = await client.actionRun({
4652
userId,
47-
actionId: "slack-send-message",
53+
actionId: "gmail-new-email-received",
4854
configuredProps,
4955
dynamicPropsId,
5056
});
57+
setErrors(result?.os)
5158
} catch (error) {
59+
setErrors(error as unknown)
5260
console.error("Action run failed:", error);
5361
}
5462
}}

packages/connect-react/src/components/ComponentForm.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type ComponentFormProps<T extends ConfigurableProps, U = ConfiguredProps<
2020
onUpdateConfiguredProps?: (v: U) => void; // XXX onChange?
2121
onUpdateDynamicProps?: (dp: DynamicProps<T>) => void;
2222
hideOptionalProps?: boolean;
23+
sdkErrors?: unknown[] | unknown | undefined;
2324
};
2425

2526
export function ComponentForm<T extends ConfigurableProps>(props: ComponentFormProps<T>) {
Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,63 @@
11
import { FormContext } from "../hooks/form-context";
22
import type { FormFieldContext } from "../hooks/form-field-context";
33
import {
4-
ConfigurableProp, ConfigurableProps,
4+
ConfigurableProp, ConfigurablePropAlert, ConfigurableProps,
55
} from "@pipedream/sdk";
6-
import { useCustomize } from "../hooks/customization-context";
7-
import type { CSSProperties } from "react";
6+
import { Alert } from "./Alert";
87

98
export type ErrorsProps<T extends ConfigurableProps, U extends ConfigurableProp> = {
10-
errors: string[];
119
field: FormFieldContext<U>;
1210
form: FormContext<T>;
1311
};
1412

1513
export function Errors<T extends ConfigurableProps, U extends ConfigurableProp>(props: ErrorsProps<T, U>) {
16-
const { errors } = props;
17-
14+
const { field } = props;
1815
const {
19-
getProps, theme,
20-
} = useCustomize();
21-
22-
const baseStyles: CSSProperties = {
23-
color: theme.colors.danger,
24-
gridArea: "errors",
25-
};
16+
errors, prop,
17+
} = field
2618

27-
if (!errors.length) {
19+
if (!Object.keys(errors || {}).length) {
2820
return null;
2921
}
3022

23+
if (!errors[prop.name]) {
24+
return null
25+
}
26+
3127
// TODO depending on type does different shit... we might need async loader around the label, etc.?
3228
// maybe that should just be handled by FormFieldContext instead of container?
29+
30+
const formattedErrors: ConfigurablePropAlert[] = []
31+
// for (const key in fieldErrors) {
32+
const key = "xxx"
33+
if (key === "sdkErrors") {
34+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
35+
// @ts-expect-error
36+
formattedErrors.push(...errors[key].map((e) => {
37+
const o = JSON.parse(e)
38+
return {
39+
type: "alert",
40+
alertType: "error",
41+
content: `#${o.name}\n${o.message}`,
42+
}
43+
}))
44+
} else {
45+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
46+
// @ts-expect-error
47+
formattedErrors.push(...errors[prop.name].map((e) => {
48+
return {
49+
type: "alert",
50+
alertType: "error",
51+
content: e,
52+
}
53+
}))
54+
}
55+
// }
56+
3357
return (
34-
<ul {...getProps("errors", baseStyles, props)}>
35-
{errors.map((msg) => <li key={msg} {...getProps("error", baseStyles, props)}>{msg}</li>)}
36-
</ul>
58+
// <ul {...getProps("errors", baseStyles, props)}>
59+
// {errors.map((msg) => <li key={msg} {...getProps("error", baseStyles, props)}>{msg}</li>)}
60+
// </ul>
61+
<>{formattedErrors.map((fe, idx: number) => <Alert prop={fe} key={idx}/>)}</>
3762
);
3863
}

packages/connect-react/src/components/Field.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ export function Field<T extends ConfigurableProp>(props: FieldProps<T>) {
4040
Label, Description, Errors,
4141
} = getComponents();
4242

43-
const errors: string[] = []; // TODO get from field context, can be added or removed by Control, etc.
44-
4543
const app = "app" in field.extra
4644
? field.extra.app
4745
: undefined;
@@ -59,12 +57,13 @@ export function Field<T extends ConfigurableProp>(props: FieldProps<T>) {
5957
// maybe that should just be handled by FormFieldContext instead of container?
6058
// XXX rename to FieldErrors + add FormErrors (to ComponentFormInternal)
6159
// XXX use similar pattern as app below for boolean and checkboxing DOM re-ordering?
60+
6261
return (
6362
<div {...getProps("field", baseStyles, props)}>
6463
<Label text={labelText} field={field} form={form} />
6564
<Control field={field} form={form} />
6665
<Description markdown={prop.description} field={field} form={form} />
67-
<Errors errors={errors} field={field} form={form} />
66+
<Errors field={field} form={form} />
6867
</div>
6968
);
7069
}

packages/connect-react/src/components/InternalField.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function InternalField<T extends ConfigurableProp>({
1515
}: FieldInternalProps<T>) {
1616
const formCtx = useFormContext();
1717
const {
18-
id: formId, configuredProps, registerField, setConfiguredProp,
18+
id: formId, configuredProps, registerField, setConfiguredProp, errors,
1919
} = formCtx;
2020

2121
const appSlug = prop.type === "app" && "app" in prop
@@ -44,6 +44,7 @@ export function InternalField<T extends ConfigurableProp>({
4444
extra: {
4545
app, // XXX fix ts
4646
},
47+
errors,
4748
};
4849
useEffect(() => registerField(fieldCtx), [
4950
fieldCtx,

packages/connect-react/src/hooks/form-context.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export const FormContextProvider = <T extends ConfigurableProps>({
7373
const id = useId();
7474

7575
const {
76-
component, configuredProps: __configuredProps, propNames, userId,
76+
component, configuredProps: __configuredProps, propNames, userId, sdkErrors,
7777
} = formProps;
7878
const componentId = component.key;
7979

@@ -94,6 +94,11 @@ export const FormContextProvider = <T extends ConfigurableProps>({
9494
setErrors,
9595
] = useState<Record<string, string[]>>({});
9696

97+
const [
98+
configurationErrors,
99+
setConfigurationErrors,
100+
] = useState<string[]>([]);
101+
97102
const [
98103
enabledOptionalProps,
99104
setEnabledOptionalProps,
@@ -264,6 +269,12 @@ export const FormContextProvider = <T extends ConfigurableProps>({
264269
_configuredProps,
265270
]);
266271

272+
useEffect(() => {
273+
handleSdkErrors(sdkErrors)
274+
}, [
275+
sdkErrors,
276+
]);
277+
267278
useEffect(() => {
268279
const newConfiguredProps: ConfiguredProps<T> = {};
269280
for (const prop of configurableProps) {
@@ -395,6 +406,28 @@ export const FormContextProvider = <T extends ConfigurableProps>({
395406
checkPropsNeedConfiguring()
396407
};
397408

409+
const handleSdkErrors = (o: unknown[] | unknown | undefined) => {
410+
if (!o) return
411+
const os = o
412+
if (Array.isArray(os) && os.length > 0) {
413+
const newErrors = os.map((it) => {
414+
const name = it.err?.name
415+
const message = it.err?.message
416+
if (name && message) return JSON.stringify({
417+
name,
418+
message,
419+
})
420+
return undefined
421+
}).filter((e) => e !== undefined)
422+
if (newErrors) setErrors({
423+
...errors,
424+
sdkErrors: newErrors,
425+
})
426+
} else if (typeof o === "object") {
427+
// TODO: handle rails api errors here
428+
}
429+
}
430+
398431
// console.log("***", configurableProps, configuredProps)
399432
const value: FormContext<T> = {
400433
id,

packages/connect-react/src/hooks/form-field-context.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type FormFieldContext<T extends ConfigurableProp> = {
1616
value: PropValue<T["type"]> | undefined;
1717
onChange: (value: PropValue<T["type"]> | undefined) => void;
1818
extra: FormFieldContextExtra<T>;
19+
errors: Record<string, string[]>;
1920
};
2021

2122
export const FormFieldContext = createContext<FormFieldContext<any /* XXX fix */> | undefined>(undefined); // eslint-disable-line @typescript-eslint/no-explicit-any

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)