Skip to content

Commit d256989

Browse files
Georgi2704Ruben van Leeuwen
authored andcommitted
Add WFO Object and Array field
1 parent 7fa5828 commit d256989

File tree

8 files changed

+1011
-71
lines changed

8 files changed

+1011
-71
lines changed

package-lock.json

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

packages/orchestrator-ui-components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"next-query-params": "^5.0.0",
4949
"object-hash": "^3.0.0",
5050
"prism-themes": "^1.9.0",
51-
"pydantic-forms": "^0.3.1",
51+
"pydantic-forms": "*",
5252
"react-diff-view": "^3.2.0",
5353
"react-draggable": "^4.4.6",
5454
"react-redux": "^9.1.2",

packages/orchestrator-ui-components/src/components/WfoPydanticForm/WfoPydanticForm.tsx

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,15 @@ import {
1515
zodValidationPresets,
1616
} from 'pydantic-forms';
1717

18-
import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
19-
2018
import { PATH_TASKS, PATH_WORKFLOWS, WfoLoading } from '@/components';
2119
import { StartWorkflowPayload } from '@/pages/processes/WfoStartProcessPage';
22-
import { HttpStatus } from '@/rtk';
20+
import { HttpStatus, isFetchBaseQueryError, isRecord } from '@/rtk';
2321
import { useStartProcessMutation } from '@/rtk/endpoints/forms';
2422
import { useAppSelector } from '@/rtk/hooks';
25-
import { FormValidationError } from '@/types';
2623

2724
import { Footer } from './Footer';
2825
import { Row } from './Row';
29-
import { Checkbox, Divider, Label, Summary, Text, TextArea } from './fields';
26+
import { Checkbox, Divider, Label, Summary, Text, TextArea, WfoObjectField, WfoArrayField} from './fields';
3027

3128
interface WfoPydanticFormProps {
3229
processName: string;
@@ -81,40 +78,26 @@ export const WfoPydanticForm = ({
8178
userInputs: [{ ...startProcessPayload }, ...requestBody],
8279
});
8380
return response
84-
.then((result) => {
85-
return new Promise<Record<string, object | string>>(
86-
(resolve) => {
87-
if (result.error) {
88-
const error =
89-
result.error as FetchBaseQueryError;
90-
if (
91-
error.status === HttpStatus.FormNotComplete
92-
) {
93-
const data = error.data as Record<
94-
string,
95-
object | string
96-
>;
97-
resolve(data);
98-
} else if (
99-
typeof error === 'object' &&
100-
error !== null
101-
) {
102-
const validationError =
103-
error as FormValidationError;
104-
if (validationError?.status === 400) {
105-
resolve({
106-
...validationError.data,
107-
status: validationError.status.toString(),
108-
});
109-
}
110-
}
111-
} else if (result.data) {
112-
resolve(result.data);
81+
.then(({ error, data }) => {
82+
return new Promise<Record<string, unknown>>((resolve) => {
83+
if (
84+
isFetchBaseQueryError(error) &&
85+
isRecord(error.data)
86+
) {
87+
if (error.status === HttpStatus.FormNotComplete) {
88+
resolve(error.data);
89+
} else if (error.status === HttpStatus.BadRequest) {
90+
resolve({
91+
...error.data,
92+
status: error.status,
93+
});
11394
}
95+
} else if (data) {
96+
resolve(data);
97+
}
11498

115-
resolve({});
116-
},
117-
);
99+
resolve({});
100+
});
118101
})
119102
.catch((error) => {
120103
return new Promise<Record<string, object>>(
@@ -159,6 +142,26 @@ export const WfoPydanticForm = ({
159142
);
160143
},
161144
},
145+
{
146+
id: 'object',
147+
ElementMatch: {
148+
isControlledElement: false,
149+
Element: WfoObjectField
150+
},
151+
matcher: (field) => {
152+
return (field.type === PydanticFormFieldType.OBJECT);
153+
}
154+
},
155+
{
156+
id: 'array',
157+
ElementMatch: {
158+
isControlledElement: true,
159+
Element: WfoArrayField
160+
},
161+
matcher: (field) => {
162+
return (field.type === PydanticFormFieldType.ARRAY);
163+
}
164+
},
162165
{
163166
id: 'summary',
164167
ElementMatch: {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useEffect } from 'react';
2+
import {
3+
fieldToComponentMatcher,
4+
itemizeArrayItem,
5+
PydanticFormElementProps, RenderFields,
6+
usePydanticFormContext,
7+
} from 'pydantic-forms';
8+
import { useFieldArray, useForm } from 'react-hook-form';
9+
import React from 'react';
10+
11+
export const WfoArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
12+
const { config, rhf } = usePydanticFormContext();
13+
const { control } = rhf;
14+
const { id: arrayName, arrayItem } = pydanticFormField;
15+
const { minItems, maxItems } = pydanticFormField.validations;
16+
17+
// const defaultValues = {
18+
// [arrayName]: [
19+
// {
20+
// ...arrayItem?.default
21+
// }
22+
// ]
23+
// };
24+
// const { control } = useForm({
25+
// defaultValues,
26+
// });
27+
const { fields, append, remove } = useFieldArray({
28+
control,
29+
name: arrayName,
30+
});
31+
32+
// const {control} = useForm({
33+
// defaultValues,
34+
// });
35+
36+
console.log("FIELDS", fields);
37+
console.log("ARRAY ITEM", arrayItem);
38+
39+
if (!arrayItem) return '';
40+
41+
// if(fields.length === 0) {
42+
// append({
43+
// [arrayName]: arrayItem.default ?? undefined,
44+
// });
45+
// }
46+
47+
const component = fieldToComponentMatcher(
48+
arrayItem,
49+
config?.componentMatcher,
50+
);
51+
52+
const renderField = (field: Record<'id', string>, index: number) => {
53+
const arrayField = itemizeArrayItem(index, arrayItem);
54+
55+
return (
56+
<div
57+
key={field.id}
58+
style={{
59+
display: 'flex',
60+
gap: '10px',
61+
alignItems: 'center',
62+
}}
63+
>
64+
<RenderFields
65+
pydanticFormComponents={[
66+
{
67+
Element: component.Element,
68+
pydanticFormField: arrayField,
69+
},
70+
]}
71+
extraTriggerFields={[arrayName]}
72+
/>
73+
{(!minItems || (minItems && fields.length > minItems)) && (
74+
<span style={{width: '20px'}} onClick={() => remove(index)}>-</span>
75+
)}
76+
</div>
77+
);
78+
};
79+
const a = fields ? fields : [{id: "aa", name: "bb", email: "cc"}];
80+
return (
81+
<div
82+
style={{
83+
padding: '1rem',
84+
display: 'flex',
85+
flexDirection: 'column',
86+
flexGrow: 1,
87+
}}
88+
>
89+
{a.map(renderField)}
90+
91+
{(!maxItems || (maxItems && fields.length < maxItems)) && (
92+
<div
93+
onClick={() => {
94+
append({
95+
[arrayName]: arrayItem.default ?? undefined,
96+
});
97+
}}
98+
style={{
99+
cursor: 'pointer',
100+
fontSize: '32px',
101+
display: 'flex',
102+
justifyContent: 'end',
103+
marginBottom: '8px',
104+
padding: '16px',
105+
}}
106+
>
107+
+
108+
</div>
109+
)}
110+
</div>
111+
);
112+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import {
3+
getPydanticFormComponents,
4+
PydanticFormElementProps,
5+
RenderFields, usePydanticFormContext,
6+
} from 'pydantic-forms';
7+
import { EuiFlexGroup } from '@elastic/eui';
8+
import { getWfoObjectFieldStyles } from '../fields/styles';
9+
10+
export const WfoObjectField = ({pydanticFormField}: PydanticFormElementProps) => {
11+
const { config } = usePydanticFormContext();
12+
const {wfoObjectFieldStyles} = getWfoObjectFieldStyles()
13+
const components = getPydanticFormComponents(
14+
pydanticFormField.properties || {},
15+
config?.componentMatcher,
16+
);
17+
18+
return <EuiFlexGroup css={wfoObjectFieldStyles}>
19+
<RenderFields pydanticFormComponents={components} />
20+
</EuiFlexGroup>
21+
}

packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ export * from './Label';
44
export * from './Divider';
55
export * from './Checkbox';
66
export * from './Summary';
7+
export * from './WfoObjectField';
8+
export * from './WfoArrayField';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { css } from '@emotion/react';
2+
3+
export const getWfoObjectFieldStyles = () => {
4+
const wfoObjectFieldStyles = css({
5+
width: '100%',
6+
'& > div': {
7+
width: '100%',
8+
},
9+
});
10+
return {
11+
wfoObjectFieldStyles
12+
};
13+
}

packages/orchestrator-ui-components/src/rtk/utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ export const mapRtkErrorToWfoError = (
8080
return error;
8181
};
8282

83+
export const isRecord = (value: unknown): value is Record<string, unknown> => {
84+
return typeof value === 'object' && value !== null && !Array.isArray(value);
85+
};
86+
87+
export const isFetchBaseQueryError = (
88+
error: FetchBaseQueryError | GraphQLError[] | SerializedError | undefined,
89+
): error is FetchBaseQueryError => {
90+
if (typeof error === 'object' && error !== null && 'status' in error) {
91+
const status = error.status;
92+
return (
93+
typeof status === 'number' ||
94+
status === 'FETCH_ERROR' ||
95+
status === 'PARSING_ERROR' ||
96+
status === 'TIMEOUT_ERROR' ||
97+
status === 'CUSTOM_ERROR'
98+
);
99+
}
100+
return false;
101+
};
102+
83103
export const getWebSocket = async (url: string) => {
84104
const session = (await getSession()) as WfoSession;
85105

0 commit comments

Comments
 (0)