diff --git a/.changeset/wicked-grapes-report.md b/.changeset/wicked-grapes-report.md new file mode 100644 index 000000000..25cf4a2f3 --- /dev/null +++ b/.changeset/wicked-grapes-report.md @@ -0,0 +1,5 @@ +--- +'@orchestrator-ui/orchestrator-ui-components': patch +--- + +WfoArrayField, WfoObjectField, fix some import and types for the forms diff --git a/package-lock.json b/package-lock.json index 0e0e74f03..fcc429fad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,7 +92,7 @@ } }, "apps/wfo-ui-surf": { - "version": "4.0.1", + "version": "4.2.2", "hasInstallScript": true, "dependencies": { "@elastic/datemath": "^5.0.3", @@ -110,6 +110,7 @@ "next-query-params": "^5.0.0", "process": "0.11.10", "prop-types": "^15.8.1", + "pydantic-forms": "0.5.1", "react": "^18.3.1", "react-dom": "^18.2.0", "react-redux": "^9.1.2", @@ -17813,10 +17814,9 @@ "license": "MIT" }, "node_modules/pydantic-forms": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/pydantic-forms/-/pydantic-forms-0.3.3.tgz", - "integrity": "sha512-c+XqEBGEhrcSG8CASVXh4cNnwbe+j/8fEM0JK8KPs+f4914OfxS3ZTLaefxiPPWIbNeGZT0cjhCfbJC0zg9zNg==", - "license": "Apache-2.0", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/pydantic-forms/-/pydantic-forms-0.5.1.tgz", + "integrity": "sha512-FoUoi+FNb/jL013uuQyolX+XnJierTDuF7/ft4ssM5GMMihA7Qx6oJhR4e4I9EqYrgbIjZsFWpleFzvvvLI8vw==", "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.7.3", "@emotion/react": "^11.14.0", @@ -22898,7 +22898,7 @@ }, "packages/orchestrator-ui-components": { "name": "@orchestrator-ui/orchestrator-ui-components", - "version": "5.2.0", + "version": "5.2.1", "license": "Apache-2.0", "dependencies": { "@elastic/eui": "101.3.0", @@ -22915,7 +22915,7 @@ "next-query-params": "^5.0.0", "object-hash": "^3.0.0", "prism-themes": "^1.9.0", - "pydantic-forms": "^0.3.1", + "pydantic-forms": "^0.5.2", "react-diff-view": "^3.2.0", "react-draggable": "^4.4.6", "react-redux": "^9.1.2", @@ -22952,6 +22952,30 @@ "react-dom": "^18.3.1" } }, + "packages/orchestrator-ui-components/node_modules/pydantic-forms": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/pydantic-forms/-/pydantic-forms-0.5.2.tgz", + "integrity": "sha512-awXx50bvReLS6om/f5rul8KITH41CLBrJ3nDEBT0PGNCRg7Kn/QN63gA/2jeRpGKCm7qEkmmtF+Yv6tMcdCPow==", + "license": "Apache-2.0", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.7.3", + "@emotion/react": "^11.14.0", + "@hookform/resolvers": "^3.9.1", + "dayjs": "^1.11.13", + "i18next": "^24.1.2", + "lodash": "^4.17.21", + "next-intl": "^3.26.5", + "react-hook-form": "^7.54.1", + "swr": "^2.3.0", + "zod": "^3.24.1", + "zod-i18n-map": "^2.27.0" + }, + "peerDependencies": { + "next": "^14.0.0 || ^15.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + } + }, "packages/tsconfig": { "name": "@orchestrator-ui/tsconfig", "version": "1.2.1", diff --git a/packages/orchestrator-ui-components/package.json b/packages/orchestrator-ui-components/package.json index 59f6aa549..07d1f8654 100644 --- a/packages/orchestrator-ui-components/package.json +++ b/packages/orchestrator-ui-components/package.json @@ -48,7 +48,7 @@ "next-query-params": "^5.0.0", "object-hash": "^3.0.0", "prism-themes": "^1.9.0", - "pydantic-forms": "^0.3.3", + "pydantic-forms": "^0.5.2", "react-diff-view": "^3.2.0", "react-draggable": "^4.4.6", "react-redux": "^9.1.2", diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/AcceptField.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/AcceptField.tsx index c33a9277c..83a6408ab 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/AcceptField.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/AcceptField.tsx @@ -21,7 +21,7 @@ import { EuiCheckbox, EuiFlexItem, EuiText } from '@elastic/eui'; import { useWithOrchestratorTheme } from '@/hooks'; -import { getStyles } from './AcceptFieldStyling'; +import { getAcceptFieldStyles } from './AcceptFieldStyling'; import { FieldProps } from './types'; type AcceptItemType = @@ -73,7 +73,7 @@ function Accept({ ...props }: AcceptFieldProps) { const t = useTranslations(); - const { acceptFieldStyle } = useWithOrchestratorTheme(getStyles); + const { acceptFieldStyle } = useWithOrchestratorTheme(getAcceptFieldStyles); const legacy = !data; const i18nBaseKey = data diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/AcceptFieldStyling.ts b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/AcceptFieldStyling.ts index 52f71c860..2c9d7ae86 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/AcceptFieldStyling.ts +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/AcceptFieldStyling.ts @@ -2,7 +2,7 @@ import { css } from '@emotion/react'; import { WfoTheme } from '@/hooks'; -export const getStyles = ({ theme }: WfoTheme) => { +export const getAcceptFieldStyles = ({ theme }: WfoTheme) => { const acceptFieldStyle = css({ '.acceptField': { 'label.warning': { diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/BoolField.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/BoolField.tsx index 4b83811c1..ab6fc7d86 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/BoolField.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/BoolField.tsx @@ -18,9 +18,9 @@ import { connectField, filterDOMProps } from 'uniforms'; import { EuiCheckbox, EuiFlexItem, EuiFormRow, EuiText } from '@elastic/eui'; +import { FieldProps } from '@/components'; import { getCommonFormFieldStyles } from '@/components/WfoForms/formFields/commonStyles'; import { useWithOrchestratorTheme } from '@/hooks'; -import { FieldProps } from '@/types'; import { boolFieldStyling } from './BoolFieldStyling'; diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/DividerField.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/DividerField.tsx index faf18c6a6..0dba1c749 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/DividerField.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/DividerField.tsx @@ -18,7 +18,7 @@ import { connectField } from 'uniforms'; import { EuiHorizontalRule } from '@elastic/eui'; -import { FieldProps } from '@/types'; +import { FieldProps } from '@/components'; export type DividerFieldProps = FieldProps; diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/ErrorField.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/ErrorField.tsx index 74bd712d5..f9fe98a4b 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/ErrorField.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/ErrorField.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { connectField, filterDOMProps } from 'uniforms'; -import { FieldProps } from '@/types'; +import { FieldProps } from '@/components'; export type ErrorFieldProps = FieldProps; diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/LabelField.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/LabelField.tsx index 3b6630fe3..01f7bb8ac 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/LabelField.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/LabelField.tsx @@ -16,8 +16,8 @@ import React from 'react'; import { connectField, filterDOMProps } from 'uniforms'; +import { FieldProps } from '@/components'; import { useOrchestratorTheme } from '@/hooks'; -import { FieldProps } from '@/types'; export type LabelFieldProps = FieldProps; diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/LongTextField.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/LongTextField.tsx index 0af49ae68..ed8b79958 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/LongTextField.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/LongTextField.tsx @@ -18,7 +18,7 @@ import { connectField, filterDOMProps } from 'uniforms'; import { EuiFormRow, EuiText, EuiTextArea } from '@elastic/eui'; -import { FieldProps } from '@/types'; +import { FieldProps } from '@/components'; export type LongTextFieldProps = FieldProps< string, diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/NumField.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/NumField.tsx index bd9aba5f3..a723c8270 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/NumField.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/NumField.tsx @@ -18,10 +18,10 @@ import { connectField, filterDOMProps } from 'uniforms'; import { EuiFieldNumber, EuiFormRow, EuiText } from '@elastic/eui'; +import { FieldProps } from '@/components'; import { getCommonFormFieldStyles } from '@/components/WfoForms/formFields/commonStyles'; import { useWithOrchestratorTheme } from '@/hooks'; import { getFormFieldsBaseStyle } from '@/theme'; -import { FieldProps } from '@/types'; export type NumFieldProps = FieldProps< number, diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/RadioField.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/RadioField.tsx index b8b779bd7..4d846800f 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/RadioField.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/RadioField.tsx @@ -19,7 +19,7 @@ import { connectField, filterDOMProps } from 'uniforms'; import { EuiFormRow, EuiRadio, EuiText } from '@elastic/eui'; -import { FieldProps } from '@/types'; +import { FieldProps } from '@/components'; const base64 = typeof btoa !== 'undefined' diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/SubmitField.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/SubmitField.tsx index 4a5ca9ca4..d2b802b10 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/SubmitField.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/SubmitField.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { filterDOMProps, useForm } from 'uniforms'; -import { FieldProps } from '@/types'; +import { FieldProps } from '@/components'; export type SubmitFieldProps = FieldProps< null, diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/TextField.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/TextField.tsx index 520cbc5d2..51ca9dd92 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/TextField.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/TextField.tsx @@ -18,10 +18,10 @@ import { connectField, filterDOMProps } from 'uniforms'; import { EuiFieldText, EuiFormRow, EuiText } from '@elastic/eui'; +import { FieldProps } from '@/components'; import { getCommonFormFieldStyles } from '@/components/WfoForms/formFields/commonStyles'; import { useWithOrchestratorTheme } from '@/hooks'; import { getFormFieldsBaseStyle } from '@/theme'; -import { FieldProps } from '@/types'; export type TextFieldProps = FieldProps; diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/ContactPersonAutocomplete.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/ContactPersonAutocomplete.tsx index 8975e3456..1cc253d5c 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/ContactPersonAutocomplete.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/ContactPersonAutocomplete.tsx @@ -22,7 +22,7 @@ import { useWithOrchestratorTheme } from '@/hooks'; import { ContactPerson } from '../types'; import { isEmpty } from '../utils'; -import { getStyles } from './ContactPersonAutocompleteStyles'; +import { getContactPersonStyles } from './ContactPersonAutocompleteStyles'; interface ContactPersonAutocompleteProps { query: string; @@ -39,8 +39,9 @@ export const ContactPersonAutocomplete = ({ itemSelected, suggestions, }: ContactPersonAutocompleteProps) => { - const { contactPersonAutocompleteStyling } = - useWithOrchestratorTheme(getStyles); + const { contactPersonAutocompleteStyling } = useWithOrchestratorTheme( + getContactPersonStyles, + ); // Intentionally not done with state since we don't need a rerender // This is only to store a ref for the scroll into view part diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/ContactPersonAutocompleteStyles.ts b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/ContactPersonAutocompleteStyles.ts index 6b4de9b3c..d5791101f 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/ContactPersonAutocompleteStyles.ts +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/ContactPersonAutocompleteStyles.ts @@ -2,7 +2,7 @@ import { css } from '@emotion/react'; import { WfoTheme } from '@/hooks'; -export const getStyles = ({ theme }: WfoTheme) => { +export const getContactPersonStyles = ({ theme }: WfoTheme) => { const contactPersonAutocompleteStyling = css` .autocomplete-container { position: relative; diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/FileUploadField.tsx b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/FileUploadField.tsx index 3c3a15806..5028268ba 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/FileUploadField.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/deprecated/FileUploadField.tsx @@ -19,10 +19,10 @@ import { connectField, filterDOMProps } from 'uniforms'; import { EuiFilePicker, EuiFormRow, EuiText } from '@elastic/eui'; +import { FieldProps } from '@/components'; import { getCommonFormFieldStyles } from '@/components/WfoForms/formFields/commonStyles'; import { useOrchestratorTheme, useWithOrchestratorTheme } from '@/hooks'; import { useUploadFileMutation } from '@/rtk/endpoints/fileUpload'; -import { FieldProps } from '@/types'; export type FileUploadProps = FieldProps; diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/index.ts b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/index.ts index 16376741a..d94886e9c 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/index.ts +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/index.ts @@ -25,3 +25,4 @@ export * from './CustomerField'; export * from './ConnectedSelectField'; export * from './deprecated/FileUploadField'; export * from './commonStyles'; +export * from './types'; diff --git a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/types.ts b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/types.ts index 3726de84e..c14236cd5 100644 --- a/packages/orchestrator-ui-components/src/components/WfoForms/formFields/types.ts +++ b/packages/orchestrator-ui-components/src/components/WfoForms/formFields/types.ts @@ -33,6 +33,7 @@ export type FieldProps< export interface ContactPerson { name: string; email: string; + phone?: string; } export interface Option { diff --git a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/Row.tsx b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/Row.tsx index 075913eac..38b44fe57 100644 --- a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/Row.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/Row.tsx @@ -4,7 +4,7 @@ import type { RowRenderComponent } from 'pydantic-forms'; import { EuiFormRow, EuiText } from '@elastic/eui'; -import { getCommonFormFieldStyles } from '@/components/WfoForms/formFields/commonStyles'; +import { getCommonFormFieldStyles } from '@/components'; import { useWithOrchestratorTheme } from '@/hooks'; export const Row: RowRenderComponent = ({ diff --git a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/WfoPydanticForm.tsx b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/WfoPydanticForm.tsx index f1bc99ad3..31d69091a 100644 --- a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/WfoPydanticForm.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/WfoPydanticForm.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { AbstractIntlMessages, useMessages, useTranslations } from 'next-intl'; import { useRouter } from 'next/router'; import type { - ComponentMatcher, + ComponentMatcherExtender, PydanticComponentMatcher, PydanticFormApiProvider, PydanticFormLabelProvider, @@ -15,19 +15,25 @@ import { zodValidationPresets, } from 'pydantic-forms'; -import { FetchBaseQueryError } from '@reduxjs/toolkit/query'; - import { PATH_TASKS, PATH_WORKFLOWS, WfoLoading } from '@/components'; import { StartWorkflowPayload } from '@/pages/processes/WfoStartProcessPage'; -import { HttpStatus } from '@/rtk'; +import { HttpStatus, isFetchBaseQueryError, isRecord } from '@/rtk'; import { useStartProcessMutation } from '@/rtk/endpoints/forms'; import { useAppSelector } from '@/rtk/hooks'; -import { FormValidationError } from '@/types'; import { Footer } from './Footer'; import { Header } from './Header'; import { Row } from './Row'; -import { Checkbox, Divider, Label, Summary, Text, TextArea } from './fields'; +import { + Checkbox, + Divider, + Label, + Summary, + Text, + TextArea, + WfoArrayField, + WfoObjectField, +} from './fields'; interface WfoPydanticFormProps { processName: string; @@ -47,8 +53,8 @@ export const WfoPydanticForm = ({ const [startProcess] = useStartProcessMutation(); const router = useRouter(); const t = useTranslations('pydanticForms.userInputForm'); - const componentMatcher = useAppSelector( - (state) => state.pydanticForm?.componentMatcher, + const componentMatcherExtender = useAppSelector( + (state) => state.pydanticForm?.componentMatcherExtender, ); const translationMessages: AbstractIntlMessages = useMessages(); @@ -82,40 +88,26 @@ export const WfoPydanticForm = ({ userInputs: [{ ...startProcessPayload }, ...requestBody], }); return response - .then((result) => { - return new Promise>( - (resolve) => { - if (result.error) { - const error = - result.error as FetchBaseQueryError; - if ( - error.status === HttpStatus.FormNotComplete - ) { - const data = error.data as Record< - string, - object | string - >; - resolve(data); - } else if ( - typeof error === 'object' && - error !== null - ) { - const validationError = - error as FormValidationError; - if (validationError?.status === 400) { - resolve({ - ...validationError.data, - status: validationError.status.toString(), - }); - } - } - } else if (result.data) { - resolve(result.data); + .then(({ error, data }) => { + return new Promise>((resolve) => { + if ( + isFetchBaseQueryError(error) && + isRecord(error.data) + ) { + if (error.status === HttpStatus.FormNotComplete) { + resolve(error.data); + } else if (error.status === HttpStatus.BadRequest) { + resolve({ + ...error.data, + status: error.status, + }); } + } else if (data) { + resolve(data); + } - resolve({}); - }, - ); + resolve({}); + }); }) .catch((error) => { return new Promise>( @@ -145,8 +137,14 @@ export const WfoPydanticForm = ({ }); }; - const wfoComponentMatcher: ComponentMatcher = (currentMatchers) => { + const wfoComponentMatcherExtender: ComponentMatcherExtender = ( + currentMatchers, + ) => { const wfoMatchers: PydanticComponentMatcher[] = [ + ...currentMatchers + .filter((matcher) => matcher.id !== 'text') + .filter((matcher) => matcher.id !== 'array') + .filter((matcher) => matcher.id !== 'object'), { id: 'textarea', ElementMatch: { @@ -160,6 +158,26 @@ export const WfoPydanticForm = ({ ); }, }, + { + id: 'object', + ElementMatch: { + isControlledElement: false, + Element: WfoObjectField, + }, + matcher: (field) => { + return field.type === PydanticFormFieldType.OBJECT; + }, + }, + { + id: 'array', + ElementMatch: { + isControlledElement: true, + Element: WfoArrayField, + }, + matcher: (field) => { + return field.type === PydanticFormFieldType.ARRAY; + }, + }, { id: 'summary', ElementMatch: { @@ -209,7 +227,6 @@ export const WfoPydanticForm = ({ return field.type === PydanticFormFieldType.BOOLEAN; }, }, - ...currentMatchers.filter((matcher) => matcher.id !== 'text'), { id: 'text', ElementMatch: { @@ -222,8 +239,9 @@ export const WfoPydanticForm = ({ validator: zodValidationPresets.string, }, ]; - - return componentMatcher ? componentMatcher(wfoMatchers) : wfoMatchers; + return componentMatcherExtender + ? componentMatcherExtender(wfoMatchers) + : wfoMatchers; }; const handleCancel = () => { @@ -243,7 +261,7 @@ export const WfoPydanticForm = ({ footerRenderer: Footer, headerRenderer: Header, skipSuccessNotice: true, - componentMatcher: wfoComponentMatcher, + componentMatcherExtender: wfoComponentMatcherExtender, labelProvider: pydanticLabelProvider, rowRenderer: Row, customTranslations: { diff --git a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/Summary.tsx b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/Summary.tsx index 1dc322d59..6ff638c9f 100644 --- a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/Summary.tsx +++ b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/Summary.tsx @@ -5,10 +5,9 @@ import type { PydanticFormElement } from 'pydantic-forms'; import { EuiFlexItem, EuiFormRow, EuiText } from '@elastic/eui'; import { tint } from '@elastic/eui'; import { css } from '@emotion/react'; -import type { WfoTheme } from '@orchestrator-ui/orchestrator-ui-components'; -import { getCommonFormFieldStyles } from '@/components/WfoForms/formFields/commonStyles'; -import { useWithOrchestratorTheme } from '@/hooks'; +import { getCommonFormFieldStyles } from '@/components'; +import { WfoTheme, useWithOrchestratorTheme } from '@/hooks'; export const getStyles = ({ theme }: WfoTheme) => { const toShadeColor = (color: string) => tint(color, 0.9); diff --git a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoArrayField/WfoArrayField.tsx b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoArrayField/WfoArrayField.tsx new file mode 100644 index 000000000..03a08721c --- /dev/null +++ b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoArrayField/WfoArrayField.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { useFieldArray } from 'react-hook-form'; + +import { + PydanticFormElementProps, + RenderFields, + fieldToComponentMatcher, + itemizeArrayItem, + usePydanticFormContext, +} from 'pydantic-forms'; + +import { EuiIcon } from '@elastic/eui'; + +import { getWfoArrayFieldStyles } from '@/components'; +import { useOrchestratorTheme } from '@/hooks'; + +export const MinusButton = ({ + index, + onRemove, +}: { + index: number; + onRemove: (index: number) => void; +}) => { + const { theme } = useOrchestratorTheme(); + const { minusButton } = getWfoArrayFieldStyles(); + + return ( + onRemove(index)}> + + + ); +}; + +export const PlusButton = ({ onClick }: { onClick: () => void }) => { + const { theme } = useOrchestratorTheme(); + const { plusButtonWrapper } = getWfoArrayFieldStyles(); + + return ( +
+ +
+ ); +}; + +export const WfoArrayField = ({ + pydanticFormField, +}: PydanticFormElementProps) => { + const { config, rhf } = usePydanticFormContext(); + const { control } = rhf; + const { id: arrayName, arrayItem } = pydanticFormField; + const { minItems, maxItems } = pydanticFormField.validations; + const { container, fieldWrapper } = getWfoArrayFieldStyles(); + + const { fields, append, remove } = useFieldArray({ + control, + name: arrayName, + }); + + const showMinus = !minItems || fields.length > minItems; + const showPlus = !maxItems || fields.length < maxItems; + + if (!arrayItem) return null; + + const component = fieldToComponentMatcher( + arrayItem, + config?.componentMatcherExtender, + ); + + const renderField = (field: Record<'id', string>, index: number) => { + //TODO: Temporary fix for wrapper showing in arrayItem when not necessary + delete arrayItem.description; + arrayItem.title = ''; + const arrayField = itemizeArrayItem(index, arrayItem); + + return ( +
+ + {showMinus && } +
+ ); + }; + + return ( +
+ {fields.map(renderField)} + + {showPlus && ( + { + append({ + [arrayName]: arrayItem.default ?? undefined, + }); + }} + /> + )} +
+ ); +}; diff --git a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoArrayField/arrayFieldStyles.ts b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoArrayField/arrayFieldStyles.ts new file mode 100644 index 000000000..19e88ff04 --- /dev/null +++ b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoArrayField/arrayFieldStyles.ts @@ -0,0 +1,34 @@ +import { css } from '@emotion/react'; + +export const getWfoArrayFieldStyles = () => { + const container = css({ + padding: '1rem', + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + }); + + const fieldWrapper = css({ + display: 'flex', + gap: '10px', + alignItems: 'center', + }); + + const minusButton = css({ + width: '40px', + cursor: 'pointer', + }); + + const plusButtonWrapper = css({ + display: 'flex', + cursor: 'pointer', + justifyContent: 'end', + }); + + return { + container, + fieldWrapper, + minusButton, + plusButtonWrapper, + }; +}; diff --git a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoArrayField/index.ts b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoArrayField/index.ts new file mode 100644 index 000000000..df421c80a --- /dev/null +++ b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoArrayField/index.ts @@ -0,0 +1,2 @@ +export * from './WfoArrayField'; +export * from './arrayFieldStyles'; diff --git a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoObjectField/WfoObjectField.tsx b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoObjectField/WfoObjectField.tsx new file mode 100644 index 000000000..176d1ecab --- /dev/null +++ b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoObjectField/WfoObjectField.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import { + PydanticFormElementProps, + RenderFields, + getPydanticFormComponents, + usePydanticFormContext, +} from 'pydantic-forms'; + +import { EuiFlexGroup } from '@elastic/eui'; + +import { getWfoObjectFieldStyles } from '@/components'; + +export const WfoObjectField = ({ + pydanticFormField, +}: PydanticFormElementProps) => { + const { config } = usePydanticFormContext(); + const { wfoObjectFieldStyles } = getWfoObjectFieldStyles(); + const components = getPydanticFormComponents( + pydanticFormField.properties || {}, + config?.componentMatcherExtender, + ); + + return ( + + + + ); +}; diff --git a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoObjectField/index.ts b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoObjectField/index.ts new file mode 100644 index 000000000..f0de63245 --- /dev/null +++ b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoObjectField/index.ts @@ -0,0 +1,2 @@ +export * from './WfoObjectField'; +export * from './objectFieldStyles'; diff --git a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoObjectField/objectFieldStyles.ts b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoObjectField/objectFieldStyles.ts new file mode 100644 index 000000000..1d78701b3 --- /dev/null +++ b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/WfoObjectField/objectFieldStyles.ts @@ -0,0 +1,13 @@ +import { css } from '@emotion/react'; + +export const getWfoObjectFieldStyles = () => { + const wfoObjectFieldStyles = css({ + width: '100%', + '& > div': { + width: '100%', + }, + }); + return { + wfoObjectFieldStyles, + }; +}; diff --git a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/index.ts b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/index.ts index 9b1aa59ec..c265474dd 100644 --- a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/index.ts +++ b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/fields/index.ts @@ -4,3 +4,5 @@ export * from './Label'; export * from './Divider'; export * from './Checkbox'; export * from './Summary'; +export * from './WfoObjectField'; +export * from './WfoArrayField'; diff --git a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/index.ts b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/index.ts index cb0adb5fc..c4591a999 100644 --- a/packages/orchestrator-ui-components/src/components/WfoPydanticForm/index.ts +++ b/packages/orchestrator-ui-components/src/components/WfoPydanticForm/index.ts @@ -1 +1,4 @@ export * from './WfoPydanticForm'; +export * from './fields'; +export * from './Row'; +export * from './Footer'; diff --git a/packages/orchestrator-ui-components/src/rtk/slices/pydanticForm.ts b/packages/orchestrator-ui-components/src/rtk/slices/pydanticForm.ts index 18c8b0bf8..0fb24b298 100644 --- a/packages/orchestrator-ui-components/src/rtk/slices/pydanticForm.ts +++ b/packages/orchestrator-ui-components/src/rtk/slices/pydanticForm.ts @@ -1,9 +1,9 @@ -import type { ComponentMatcher } from 'pydantic-forms'; +import type { ComponentMatcherExtender } from 'pydantic-forms'; import { Slice, createSlice } from '@reduxjs/toolkit'; export type PydanticForm = { - componentMatcher?: ComponentMatcher; + componentMatcherExtender?: ComponentMatcherExtender; }; type PydanticFormComponentMatcherSlice = Slice; diff --git a/packages/orchestrator-ui-components/src/rtk/storeProvider.tsx b/packages/orchestrator-ui-components/src/rtk/storeProvider.tsx index 3e92c748a..4141ccea4 100644 --- a/packages/orchestrator-ui-components/src/rtk/storeProvider.tsx +++ b/packages/orchestrator-ui-components/src/rtk/storeProvider.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import type { ReactNode } from 'react'; import { Provider } from 'react-redux'; -import type { ComponentMatcher } from 'pydantic-forms'; +import type { ComponentMatcherExtender } from 'pydantic-forms'; import { emptyOrchestratorConfig } from '@/contexts'; import { CustomApiConfig } from '@/rtk/slices/customApis'; @@ -14,7 +14,7 @@ import { getOrchestratorStore } from './store'; export type StoreProviderProps = { initialOrchestratorConfig: OrchestratorConfig | null; orchestratorComponentOverride?: OrchestratorComponentOverride; - componentMatcher?: ComponentMatcher; + componentMatcherExtender?: ComponentMatcherExtender; customApis?: CustomApiConfig[]; children: ReactNode; }; @@ -22,7 +22,7 @@ export type StoreProviderProps = { export const StoreProvider = ({ initialOrchestratorConfig, orchestratorComponentOverride, - componentMatcher, + componentMatcherExtender, customApis = [], children, }: StoreProviderProps) => { @@ -31,7 +31,7 @@ export const StoreProvider = ({ initialOrchestratorConfig ?? emptyOrchestratorConfig, orchestratorComponentOverride, pydanticForm: { - componentMatcher: componentMatcher || undefined, + componentMatcherExtender: componentMatcherExtender || undefined, }, customApis, }); diff --git a/packages/orchestrator-ui-components/src/rtk/utils.ts b/packages/orchestrator-ui-components/src/rtk/utils.ts index cd47761c1..b05ace175 100644 --- a/packages/orchestrator-ui-components/src/rtk/utils.ts +++ b/packages/orchestrator-ui-components/src/rtk/utils.ts @@ -80,6 +80,26 @@ export const mapRtkErrorToWfoError = ( return error; }; +export const isRecord = (value: unknown): value is Record => { + return typeof value === 'object' && value !== null && !Array.isArray(value); +}; + +export const isFetchBaseQueryError = ( + error: FetchBaseQueryError | GraphQLError[] | SerializedError | undefined, +): error is FetchBaseQueryError => { + if (typeof error === 'object' && error !== null && 'status' in error) { + const status = error.status; + return ( + typeof status === 'number' || + status === 'FETCH_ERROR' || + status === 'PARSING_ERROR' || + status === 'TIMEOUT_ERROR' || + status === 'CUSTOM_ERROR' + ); + } + return false; +}; + export const getWebSocket = async (url: string) => { const session = (await getSession()) as WfoSession; diff --git a/packages/orchestrator-ui-components/src/types/forms.ts b/packages/orchestrator-ui-components/src/types/forms.ts index 11647e964..4c4109d77 100644 --- a/packages/orchestrator-ui-components/src/types/forms.ts +++ b/packages/orchestrator-ui-components/src/types/forms.ts @@ -1,7 +1,4 @@ -import { Ref } from 'react'; - import { JSONSchema6 } from 'json-schema'; -import { HTMLFieldProps } from 'uniforms'; import { HttpStatus } from '@/rtk'; @@ -27,20 +24,6 @@ export interface FormNotCompleteResponse { meta?: { hasNext?: boolean }; } -export type FieldProps< - Value, - Extra = object, - InputElementType = HTMLInputElement, - ElementType = HTMLDivElement, -> = HTMLFieldProps< - Value, - ElementType, - { - inputRef?: Ref; - description?: string; - } & Extra ->; - type ValidationErrorData = { detail: string; status: HttpStatus;