From cf22edfcf58199285d076e07155e8d4578194127 Mon Sep 17 00:00:00 2001 From: Christian Vogt Date: Fri, 16 Aug 2024 19:32:06 -0400 Subject: [PATCH] model to manage field creation (#3100) --- .../userManagement/userManagement.cy.ts | 2 +- .../fields/BooleanFormField.tsx | 58 +++---- .../fields/ConnectionTypeDataFormField.tsx | 68 +++----- .../fields/ConnectionTypeFormFields.tsx | 17 +- .../fields/DataFormFieldGroup.tsx | 51 +++--- .../fields/DefaultValueTextRenderer.tsx | 24 +++ .../fields/DropdownFormField.tsx | 145 +++++++++--------- .../connectionTypes/fields/FileFormField.tsx | 70 ++++----- .../fields/HiddenFormField.tsx | 31 ++-- .../fields/NumericFormField.tsx | 31 ++-- .../fields/SectionFormField.tsx | 9 +- .../fields/ShortTextFormField.tsx | 31 ++-- .../connectionTypes/fields/TextFormField.tsx | 23 ++- .../connectionTypes/fields/UriFormField.tsx | 81 ++++++++-- .../__tests__/BooleanFormField.spec.tsx | 8 +- .../ConnectionTypeDataFormField.spec.tsx | 23 ++- .../__tests__/DataFormFieldGroup.spec.tsx | 82 ---------- .../DefaultValueTextRenderer.spec.tsx | 49 ++++++ .../__tests__/DropdownFormField.spec.tsx | 44 +++++- .../fields/__tests__/FileFormField.spec.tsx | 14 +- .../fields/__tests__/HiddenFormField.spec.tsx | 20 ++- .../__tests__/NumericFormField.spec.tsx | 8 +- .../__tests__/ShortTextFormField.spec.tsx | 14 +- .../fields/__tests__/TextFormField.spec.tsx | 14 +- .../fields/__tests__/UriFormField.spec.tsx | 14 +- .../connectionTypes/fields/types.d.ts | 11 ++ .../ConnectionTypeDataFieldModal.tsx | 131 +++++++--------- .../manage/ConnectionTypeFieldModal.tsx | 2 +- .../manage/ConnectionTypeSectionModal.tsx | 6 +- .../manage/DataFieldPropertiesForm.tsx | 59 +++++++ .../ConnectionTypeDataFieldModal.spec.tsx} | 50 ++---- .../BooleanAdvancedPropertiesForm.tsx | 32 ++++ .../DataFieldAdvancedPropertiesForm.tsx | 36 +++++ .../manage/advanced/types.d.ts | 6 + 34 files changed, 727 insertions(+), 537 deletions(-) create mode 100644 frontend/src/concepts/connectionTypes/fields/DefaultValueTextRenderer.tsx delete mode 100644 frontend/src/concepts/connectionTypes/fields/__tests__/DataFormFieldGroup.spec.tsx create mode 100644 frontend/src/concepts/connectionTypes/fields/__tests__/DefaultValueTextRenderer.spec.tsx create mode 100644 frontend/src/concepts/connectionTypes/fields/types.d.ts rename frontend/src/pages/connectionTypes/{fields => manage}/ConnectionTypeDataFieldModal.tsx (70%) create mode 100644 frontend/src/pages/connectionTypes/manage/DataFieldPropertiesForm.tsx rename frontend/src/pages/connectionTypes/{fields/__tests__/AddConnectionTypeFieldModal.spec.tsx => manage/__tests__/ConnectionTypeDataFieldModal.spec.tsx} (56%) create mode 100644 frontend/src/pages/connectionTypes/manage/advanced/BooleanAdvancedPropertiesForm.tsx create mode 100644 frontend/src/pages/connectionTypes/manage/advanced/DataFieldAdvancedPropertiesForm.tsx create mode 100644 frontend/src/pages/connectionTypes/manage/advanced/types.d.ts diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/userManagement/userManagement.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/userManagement/userManagement.cy.ts index 9283c0c224..7e6274f6fc 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/userManagement/userManagement.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/userManagement/userManagement.cy.ts @@ -44,7 +44,7 @@ describe('User Management', () => { userGroupSection.findChipItem('system:authenticated').should('exist'); userGroupSection.clearMultiChipItem(); userGroupSection.findErrorText().should('exist'); - userGroupSection.selectMultiGroup('odh-admins', false); + userGroupSection.selectMultiGroup('odh-admins'); userGroupSection.findChipItem(/^odh-admins$/).should('exist'); userGroupSection.findMultiGroupSelectButton().click(); userManagement.findSubmitButton().should('be.enabled'); diff --git a/frontend/src/concepts/connectionTypes/fields/BooleanFormField.tsx b/frontend/src/concepts/connectionTypes/fields/BooleanFormField.tsx index 2f60d3f762..28875c0ace 100644 --- a/frontend/src/concepts/connectionTypes/fields/BooleanFormField.tsx +++ b/frontend/src/concepts/connectionTypes/fields/BooleanFormField.tsx @@ -1,36 +1,36 @@ import * as React from 'react'; import { Checkbox } from '@patternfly/react-core'; import { BooleanField } from '~/concepts/connectionTypes/types'; -import DataFormFieldGroup from '~/concepts/connectionTypes/fields/DataFormFieldGroup'; +import { FieldProps } from '~/concepts/connectionTypes/fields/types'; -type Props = { - field: BooleanField; - isPreview?: boolean; - value?: boolean; - onChange?: (value: boolean) => void; +const BooleanFormField: React.FC> = ({ + id, + field, + mode, + onChange, + value, + 'data-testid': dataTestId, +}) => { + const isPreview = mode === 'preview'; + return ( + undefined + : (_e, v) => onChange(v) + } + /> + ); }; -const BooleanFormField: React.FC = ({ field, isPreview, onChange, value }) => ( - - {(id) => ( - undefined - : (_e, v) => onChange(v) - } - /> - )} - -); - export default BooleanFormField; diff --git a/frontend/src/concepts/connectionTypes/fields/ConnectionTypeDataFormField.tsx b/frontend/src/concepts/connectionTypes/fields/ConnectionTypeDataFormField.tsx index 7a13c20830..fec74a5a3f 100644 --- a/frontend/src/concepts/connectionTypes/fields/ConnectionTypeDataFormField.tsx +++ b/frontend/src/concepts/connectionTypes/fields/ConnectionTypeDataFormField.tsx @@ -8,54 +8,30 @@ import TextFormField from '~/concepts/connectionTypes/fields/TextFormField'; import ShortTextFormField from '~/concepts/connectionTypes/fields/ShortTextFormField'; import UriFormField from '~/concepts/connectionTypes/fields/UriFormField'; import { ConnectionTypeDataField, ConnectionTypeFieldType } from '~/concepts/connectionTypes/types'; - -type Props = { - field: T; - isPreview?: boolean; - onChange?: (field: T, value: unknown) => void; - value?: T['properties']['defaultValue']; +import { FieldProps } from '~/concepts/connectionTypes/fields/types'; + +const components = { + [ConnectionTypeFieldType.ShortText]: ShortTextFormField, + [ConnectionTypeFieldType.Text]: TextFormField, + [ConnectionTypeFieldType.URI]: UriFormField, + [ConnectionTypeFieldType.Hidden]: HiddenFormField, + [ConnectionTypeFieldType.File]: FileFormField, + [ConnectionTypeFieldType.Boolean]: BooleanFormField, + [ConnectionTypeFieldType.Numeric]: NumericFormField, + [ConnectionTypeFieldType.Dropdown]: DropdownFormField, }; -const ConnectionTypeDataFormField = ({ - field, - isPreview, - onChange, - value, -}: Props): React.ReactNode => { - const commonProps = { - isPreview, - onChange: onChange ? (v: unknown) => onChange(field, v) : undefined, - // even though the value is the type of the field default value, typescript cannot determine this here - // or when applied to the element itself within the switch statement - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions,@typescript-eslint/no-explicit-any - value: value as any, - }; - switch (field.type) { - case ConnectionTypeFieldType.ShortText: - return ; - - case ConnectionTypeFieldType.Text: - return ; - - case ConnectionTypeFieldType.URI: - return ; - - case ConnectionTypeFieldType.Hidden: - return ; - - case ConnectionTypeFieldType.File: - return ; - - case ConnectionTypeFieldType.Boolean: - return ; - - case ConnectionTypeFieldType.Numeric: - return ; - - case ConnectionTypeFieldType.Dropdown: - return ; - } - return null; +const ConnectionTypeDataFormField = ( + props: FieldProps, +): React.ReactNode => { + const Component = components[props.field.type]; + return ( + + ); }; export default ConnectionTypeDataFormField; diff --git a/frontend/src/concepts/connectionTypes/fields/ConnectionTypeFormFields.tsx b/frontend/src/concepts/connectionTypes/fields/ConnectionTypeFormFields.tsx index a4397a7b39..a05ba90d06 100644 --- a/frontend/src/concepts/connectionTypes/fields/ConnectionTypeFormFields.tsx +++ b/frontend/src/concepts/connectionTypes/fields/ConnectionTypeFormFields.tsx @@ -1,6 +1,7 @@ import { FormSection } from '@patternfly/react-core'; import * as React from 'react'; import ConnectionTypeDataFormField from '~/concepts/connectionTypes/fields/ConnectionTypeDataFormField'; +import DataFormFieldGroup from '~/concepts/connectionTypes/fields/DataFormFieldGroup'; import SectionFormField from '~/concepts/connectionTypes/fields/SectionFormField'; import { ConnectionTypeDataField, @@ -35,12 +36,16 @@ const ConnectionTypeFormFields: React.FC = ({ fields, isPreview, onChange const renderDataFields = (dataFields: ConnectionTypeDataField[]) => dataFields.map((field, i) => ( - + + {(id) => ( + onChange(field, v) : undefined} + /> + )} + )); return ( diff --git a/frontend/src/concepts/connectionTypes/fields/DataFormFieldGroup.tsx b/frontend/src/concepts/connectionTypes/fields/DataFormFieldGroup.tsx index 2fe67db210..7ab2dd984e 100644 --- a/frontend/src/concepts/connectionTypes/fields/DataFormFieldGroup.tsx +++ b/frontend/src/concepts/connectionTypes/fields/DataFormFieldGroup.tsx @@ -1,41 +1,26 @@ import * as React from 'react'; -import { FormGroup } from '@patternfly/react-core'; +import { FormGroup, GenerateId } from '@patternfly/react-core'; import { ConnectionTypeDataField } from '~/concepts/connectionTypes/types'; -import FormGroupText from '~/components/FormGroupText'; -import UnspecifiedValue from '~/concepts/connectionTypes/fields/UnspecifiedValue'; -import { defaultValueToString } from '~/concepts/connectionTypes/utils'; -type Props = { - field: T; - isPreview: boolean; +type Props = { + field: ConnectionTypeDataField; children: (id: string) => React.ReactNode; - renderDefaultValue?: boolean; }; -const DataFormFieldGroup = ({ - field, - isPreview, - children, - renderDefaultValue = true, -}: Props): React.ReactNode => { - const id = `${field.type}-${field.envVar}`; - return ( - - {field.properties.defaultReadOnly && renderDefaultValue ? ( - - {defaultValueToString(field) ?? (isPreview ? : '-')} - - ) : ( - children(id) - )} - - ); -}; +const DataFormFieldGroup: React.FC = ({ field, children }): React.ReactNode => ( + + {(id) => ( + + {children(id)} + + )} + +); export default DataFormFieldGroup; diff --git a/frontend/src/concepts/connectionTypes/fields/DefaultValueTextRenderer.tsx b/frontend/src/concepts/connectionTypes/fields/DefaultValueTextRenderer.tsx new file mode 100644 index 0000000000..7ffdc17d25 --- /dev/null +++ b/frontend/src/concepts/connectionTypes/fields/DefaultValueTextRenderer.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import FormGroupText from '~/components/FormGroupText'; +import { FieldMode } from '~/concepts/connectionTypes/fields/types'; +import UnspecifiedValue from '~/concepts/connectionTypes/fields/UnspecifiedValue'; +import { ConnectionTypeDataField } from '~/concepts/connectionTypes/types'; +import { defaultValueToString } from '~/concepts/connectionTypes/utils'; + +type Props = { + id: string; + field: ConnectionTypeDataField; + mode?: FieldMode; + children: React.ReactNode; +}; + +const DefaultValueTextRenderer: React.FC = ({ id, field, mode, children }) => + mode !== 'default' && field.properties.defaultReadOnly ? ( + + {defaultValueToString(field) ?? (mode === 'preview' ? : '-')} + + ) : ( + children + ); + +export default DefaultValueTextRenderer; diff --git a/frontend/src/concepts/connectionTypes/fields/DropdownFormField.tsx b/frontend/src/concepts/connectionTypes/fields/DropdownFormField.tsx index 5a6032d23c..c9e1de730f 100644 --- a/frontend/src/concepts/connectionTypes/fields/DropdownFormField.tsx +++ b/frontend/src/concepts/connectionTypes/fields/DropdownFormField.tsx @@ -1,87 +1,88 @@ import * as React from 'react'; import { Badge, MenuToggle, Select, SelectList, SelectOption } from '@patternfly/react-core'; import { DropdownField } from '~/concepts/connectionTypes/types'; -import DataFormFieldGroup from '~/concepts/connectionTypes/fields/DataFormFieldGroup'; +import { FieldProps } from '~/concepts/connectionTypes/fields/types'; +import DefaultValueTextRenderer from '~/concepts/connectionTypes/fields/DefaultValueTextRenderer'; -type Props = { - field: DropdownField; - isPreview?: boolean; - onChange?: (value: string[]) => void; - value?: string[]; -}; - -const DropdownFormField: React.FC = ({ field, isPreview, onChange, value }) => { +const DropdownFormField: React.FC> = ({ + id, + field, + mode, + onChange, + value, + 'data-testid': dataTestId, +}) => { + const isPreview = mode === 'preview'; const [isOpen, setIsOpen] = React.useState(false); const isMulti = field.properties.variant === 'multi'; const selected = isPreview ? field.properties.defaultValue : value; return ( - - {(id) => ( - { + if (isMulti) { + if (selected?.includes(String(v))) { + onChange(selected.filter((s) => s !== v)); } else { - onChange([String(v)]); - setIsOpen(false); + onChange([...(selected || []), String(v)]); } + } else { + onChange([String(v)]); + setIsOpen(false); } - } - onOpenChange={(open) => setIsOpen(open)} - toggle={(toggleRef) => ( - { - setIsOpen((open) => !open); - }} - isExpanded={isOpen} + } + } + onOpenChange={(open) => setIsOpen(open)} + toggle={(toggleRef) => ( + { + setIsOpen((open) => !open); + }} + isExpanded={isOpen} + > + {isMulti ? ( + <> + Count{' '} + + {(isPreview ? field.properties.defaultValue?.length : value?.length) ?? 0}{' '} + selected + + + ) : ( + (isPreview + ? field.properties.items?.find( + (i) => i.value === field.properties.defaultValue?.[0], + )?.label + : field.properties.items?.find((i) => value?.includes(i.value))?.label) || + 'Select a value' + )} + + )} + > + + {field.properties.items?.map((i) => ( + - {isMulti ? ( - <> - Count{' '} - - {(isPreview ? field.properties.defaultValue?.length : value?.length) ?? 0}{' '} - selected - - - ) : ( - (isPreview - ? field.properties.items?.find( - (i) => i.value === field.properties.defaultValue?.[0], - )?.label - : field.properties.items?.find((i) => value?.includes(i.value))?.label) || - 'Select a value' - )} - - )} - > - - {field.properties.items?.map((i) => ( - - {i.label} - - ))} - - - )} - + {i.label} + + ))} + + + ); }; diff --git a/frontend/src/concepts/connectionTypes/fields/FileFormField.tsx b/frontend/src/concepts/connectionTypes/fields/FileFormField.tsx index 2ffb22052b..340a876aea 100644 --- a/frontend/src/concepts/connectionTypes/fields/FileFormField.tsx +++ b/frontend/src/concepts/connectionTypes/fields/FileFormField.tsx @@ -1,48 +1,44 @@ import * as React from 'react'; import { FileUpload } from '@patternfly/react-core'; import { FileField } from '~/concepts/connectionTypes/types'; -import DataFormFieldGroup from '~/concepts/connectionTypes/fields/DataFormFieldGroup'; +import { FieldProps } from '~/concepts/connectionTypes/fields/types'; -type Props = { - field: FileField; - isPreview?: boolean; - value?: string; - onChange?: (value: string) => void; -}; - -const FileFormField: React.FC = ({ field, isPreview, onChange, value }) => { +const FileFormField: React.FC> = ({ + id, + field, + mode, + onChange, + value, + 'data-testid': dataTestId, +}) => { + const isPreview = mode === 'preview'; const [isLoading, setIsLoading] = React.useState(false); const [filename, setFilename] = React.useState(''); const readOnly = isPreview || field.properties.defaultReadOnly; return ( - - {(id) => ( - onChange(content)} - onFileInputChange={(_e, file) => setFilename(file.name)} - onReadStarted={() => { - setIsLoading(true); - }} - onReadFinished={() => { - setIsLoading(false); - }} - /> - )} - + onChange(content)} + onFileInputChange={(_e, file) => setFilename(file.name)} + onReadStarted={() => { + setIsLoading(true); + }} + onReadFinished={() => { + setIsLoading(false); + }} + /> ); }; diff --git a/frontend/src/concepts/connectionTypes/fields/HiddenFormField.tsx b/frontend/src/concepts/connectionTypes/fields/HiddenFormField.tsx index 169c51fbfc..8e1ea1ce87 100644 --- a/frontend/src/concepts/connectionTypes/fields/HiddenFormField.tsx +++ b/frontend/src/concepts/connectionTypes/fields/HiddenFormField.tsx @@ -1,31 +1,34 @@ import * as React from 'react'; import { HiddenField } from '~/concepts/connectionTypes/types'; import PasswordInput from '~/components/PasswordInput'; -import DataFormFieldGroup from '~/concepts/connectionTypes/fields/DataFormFieldGroup'; +import { FieldProps } from '~/concepts/connectionTypes/fields/types'; +import DefaultValueTextRenderer from '~/concepts/connectionTypes/fields/DefaultValueTextRenderer'; -type Props = { - field: HiddenField; - isPreview?: boolean; - value?: string; - onChange?: (value: string) => void; -}; - -const HiddenFormField: React.FC = ({ field, isPreview, onChange, value }) => ( - - {(id) => ( +const HiddenFormField: React.FC> = ({ + id, + field, + mode, + onChange, + value, + 'data-testid': dataTestId, +}) => { + const isPreview = mode === 'preview'; + return ( + onChange(v)} /> - )} - -); + + ); +}; export default HiddenFormField; diff --git a/frontend/src/concepts/connectionTypes/fields/NumericFormField.tsx b/frontend/src/concepts/connectionTypes/fields/NumericFormField.tsx index 54a1975b40..cc981ac188 100644 --- a/frontend/src/concepts/connectionTypes/fields/NumericFormField.tsx +++ b/frontend/src/concepts/connectionTypes/fields/NumericFormField.tsx @@ -1,23 +1,26 @@ import * as React from 'react'; import { NumericField } from '~/concepts/connectionTypes/types'; import NumberInputWrapper from '~/components/NumberInputWrapper'; -import DataFormFieldGroup from '~/concepts/connectionTypes/fields/DataFormFieldGroup'; +import { FieldProps } from '~/concepts/connectionTypes/fields/types'; +import DefaultValueTextRenderer from '~/concepts/connectionTypes/fields/DefaultValueTextRenderer'; -type Props = { - field: NumericField; - isPreview?: boolean; - value?: number; - onChange?: (value: number) => void; -}; - -const NumericFormField: React.FC = ({ field, isPreview, onChange, value }) => ( - - {(id) => ( +const NumericFormField: React.FC> = ({ + id, + field, + mode, + onChange, + value, + 'data-testid': dataTestId, +}) => { + const isPreview = mode === 'preview'; + return ( + = ({ field, isPreview, onChange, value } // NumberInput shows a disabled input if no onChange provided onChange={isPreview || !onChange ? () => undefined : onChange} /> - )} - -); + + ); +}; export default NumericFormField; diff --git a/frontend/src/concepts/connectionTypes/fields/SectionFormField.tsx b/frontend/src/concepts/connectionTypes/fields/SectionFormField.tsx index 5f8cac18b6..0dfdafe447 100644 --- a/frontend/src/concepts/connectionTypes/fields/SectionFormField.tsx +++ b/frontend/src/concepts/connectionTypes/fields/SectionFormField.tsx @@ -5,10 +5,15 @@ import FormSection from '~/components/pf-overrides/FormSection'; type Props = { field: SectionField; children?: React.ReactNode; + 'data-testid'?: string; }; -const SectionFormField: React.FC = ({ field: { name, description }, children }) => ( - +const SectionFormField: React.FC = ({ + field: { name, description }, + children, + 'data-testid': dataTestId, +}) => ( + {children} ); diff --git a/frontend/src/concepts/connectionTypes/fields/ShortTextFormField.tsx b/frontend/src/concepts/connectionTypes/fields/ShortTextFormField.tsx index 99a03f5c2d..d8a86d5214 100644 --- a/frontend/src/concepts/connectionTypes/fields/ShortTextFormField.tsx +++ b/frontend/src/concepts/connectionTypes/fields/ShortTextFormField.tsx @@ -1,29 +1,32 @@ import * as React from 'react'; import { TextInput } from '@patternfly/react-core'; import { ShortTextField } from '~/concepts/connectionTypes/types'; -import DataFormFieldGroup from '~/concepts/connectionTypes/fields/DataFormFieldGroup'; +import { FieldProps } from '~/concepts/connectionTypes/fields/types'; +import DefaultValueTextRenderer from '~/concepts/connectionTypes/fields/DefaultValueTextRenderer'; -type Props = { - field: ShortTextField; - isPreview?: boolean; - value?: string; - onChange?: (value: string) => void; -}; - -const ShortTextFormField: React.FC = ({ field, isPreview, onChange, value }) => ( - - {(id) => ( +const ShortTextFormField: React.FC> = ({ + id, + field, + mode, + onChange, + value, + 'data-testid': dataTestId, +}) => { + const isPreview = mode === 'preview'; + return ( + onChange(v)} /> - )} - -); + + ); +}; export default ShortTextFormField; diff --git a/frontend/src/concepts/connectionTypes/fields/TextFormField.tsx b/frontend/src/concepts/connectionTypes/fields/TextFormField.tsx index ac101ea88b..d2d21921fe 100644 --- a/frontend/src/concepts/connectionTypes/fields/TextFormField.tsx +++ b/frontend/src/concepts/connectionTypes/fields/TextFormField.tsx @@ -1,18 +1,13 @@ import * as React from 'react'; import { TextArea } from '@patternfly/react-core'; import { TextField } from '~/concepts/connectionTypes/types'; -import DataFormFieldGroup from '~/concepts/connectionTypes/fields/DataFormFieldGroup'; +import { FieldProps } from '~/concepts/connectionTypes/fields/types'; +import DefaultValueTextRenderer from '~/concepts/connectionTypes/fields/DefaultValueTextRenderer'; -type Props = { - field: TextField; - isPreview?: boolean; - value?: string; - onChange?: (value: string) => void; -}; - -const TextFormField: React.FC = ({ field, isPreview, onChange, value }) => ( - - {(id) => ( +const TextFormField: React.FC> = ({ id, field, mode, onChange, value }) => { + const isPreview = mode === 'preview'; + return ( +